Enable Uri escape function call

This commit is contained in:
Sam Xu 2019-09-26 12:57:15 -07:00
parent 740123d36a
commit 35a35a5f56
7 changed files with 182 additions and 7 deletions

View file

@ -18,6 +18,25 @@ namespace Microsoft.OpenApi.OData.Edm
/// </summary>
public static class EdmModelExtensions
{
/// <summary>
/// Determines whether the specified operation is UrlEscape function or not.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="operation">The specified operation.</param>
/// <returns><c>true</c> if the specified operation is UrlEscape function; otherwise, <c>false</c>.</returns>
public static bool IsUrlEscapeFunction(this IEdmModel model, IEdmOperation operation)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(operation, nameof(operation));
if (operation.IsAction())
{
return false;
}
return model.IsUrlEscapeFunction((IEdmFunction)operation);
}
/// <summary>
/// Determines whether the specified function is UrlEscape function or not.
/// </summary>

View file

@ -22,8 +22,18 @@ namespace Microsoft.OpenApi.OData.Edm
/// </summary>
/// <param name="operation">The operation.</param>
public ODataOperationSegment(IEdmOperation operation)
: this(operation, false)
{
}
/// <summary>
/// Initializes a new instance of <see cref="ODataOperationSegment"/> class.
/// </summary>
/// <param name="operation">The operation.</param>
public ODataOperationSegment(IEdmOperation operation, bool isEscapedFunction)
{
Operation = operation ?? throw Error.ArgumentNull(nameof(operation));
IsEscapedFunction = isEscapedFunction;
}
/// <summary>
@ -31,6 +41,11 @@ namespace Microsoft.OpenApi.OData.Edm
/// </summary>
public IEdmOperation Operation { get; }
/// <summary>
/// Gets the is escaped function.
/// </summary>
public bool IsEscapedFunction { get; }
/// <inheritdoc />
public override ODataSegmentKind Kind => ODataSegmentKind.Operation;
@ -52,6 +67,22 @@ namespace Microsoft.OpenApi.OData.Edm
private string FunctionName(IEdmFunction function, OpenApiConvertSettings settings, HashSet<string> parameters)
{
if (settings.EnableUriEscapeFunctionCall && IsEscapedFunction)
{
// Debug.Assert(function.Parameters.Count == 2); It should be verify at Edm model.
// Debug.Assert(function.IsBound == true);
string parameterName = function.Parameters.Last().Name;
parameterName = Utils.GetUniqueName(parameterName, parameters);
if (function.IsComposable)
{
return $"{{{parameterName}}}:";
}
else
{
return $"{{{parameterName}}}";
}
}
StringBuilder functionName = new StringBuilder();
if (settings.EnableUnqualifiedCall)
{

View file

@ -159,6 +159,17 @@ namespace Microsoft.OpenApi.OData.Edm
}
else // other segments
{
if (segment.Kind == ODataSegmentKind.Operation)
{
ODataOperationSegment operation = (ODataOperationSegment)segment;
if (operation.IsEscapedFunction && settings.EnableUriEscapeFunctionCall)
{
sb.Append(":/");
sb.Append(pathItemName);
continue;
}
}
sb.Append("/");
sb.Append(pathItemName);
}

View file

@ -319,15 +319,18 @@ namespace Microsoft.OpenApi.OData.Edm
private bool AppendBoundOperationOnNavigationSourcePath(IEdmOperation edmOperation, bool isCollection, IEdmEntityType bindingEntityType)
{
bool found = false;
if (_allNavigationSourcePaths.TryGetValue(bindingEntityType, out IList<ODataPath> value))
{
bool isEscapedFunction = _model.IsUrlEscapeFunction(edmOperation);
foreach (var subPath in value)
{
if ((isCollection && subPath.Kind == ODataPathKind.EntitySet) ||
(!isCollection && subPath.Kind != ODataPathKind.EntitySet))
{
ODataPath newPath = subPath.Clone();
newPath.Push(new ODataOperationSegment(edmOperation));
newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction));
AppendPath(newPath);
found = true;
}
@ -340,6 +343,7 @@ namespace Microsoft.OpenApi.OData.Edm
private bool AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOperation, bool isCollection, IEdmEntityType bindingEntityType)
{
bool found = false;
bool isEscapedFunction = _model.IsUrlEscapeFunction(edmOperation);
if (_allNavigationPropertyPaths.TryGetValue(bindingEntityType, out IList<ODataPath> value))
{
@ -370,7 +374,7 @@ namespace Microsoft.OpenApi.OData.Edm
}
ODataPath newPath = path.Clone();
newPath.Push(new ODataOperationSegment(edmOperation));
newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction));
AppendPath(newPath);
found = true;
}
@ -383,6 +387,7 @@ namespace Microsoft.OpenApi.OData.Edm
{
bool found = false;
bool isEscapedFunction = _model.IsUrlEscapeFunction(edmOperation);
foreach (var baseType in bindingEntityType.FindAllBaseTypes())
{
if (_allNavigationSources.TryGetValue(baseType, out IList<IEdmNavigationSource> baseNavigationSource))
@ -394,7 +399,7 @@ namespace Microsoft.OpenApi.OData.Edm
if (ns is IEdmEntitySet)
{
ODataPath newPath = new ODataPath(new ODataNavigationSourceSegment(ns), new ODataTypeCastSegment(bindingEntityType),
new ODataOperationSegment(edmOperation));
new ODataOperationSegment(edmOperation, isEscapedFunction));
AppendPath(newPath);
found = true;
}
@ -404,7 +409,7 @@ namespace Microsoft.OpenApi.OData.Edm
if (ns is IEdmSingleton)
{
ODataPath newPath = new ODataPath(new ODataNavigationSourceSegment(ns), new ODataTypeCastSegment(bindingEntityType),
new ODataOperationSegment(edmOperation));
new ODataOperationSegment(edmOperation, isEscapedFunction));
AppendPath(newPath);
found = true;
}
@ -412,7 +417,7 @@ namespace Microsoft.OpenApi.OData.Edm
{
ODataPath newPath = new ODataPath(new ODataNavigationSourceSegment(ns), new ODataKeySegment(ns.EntityType()),
new ODataTypeCastSegment(bindingEntityType),
new ODataOperationSegment(edmOperation));
new ODataOperationSegment(edmOperation, isEscapedFunction));
AppendPath(newPath);
found = true;
}

View file

@ -68,6 +68,11 @@ namespace Microsoft.OpenApi.OData
/// </summary>
public bool EnableOperationId { get; set; } = true;
/// <summary>
/// Gets/sets a value indicating whether to output the binding function as Uri escape function if applied the UriEscapeFunction term.
/// </summary>
public bool EnableUriEscapeFunctionCall { get; set; } = false;
/// <summary>
/// Gets/sets a value indicating whether to verify the edm model before converter.
/// </summary>
@ -103,6 +108,7 @@ namespace Microsoft.OpenApi.OData
newSettings.VerifyEdmModel = this.VerifyEdmModel;
newSettings.IEEE754Compatible = this.IEEE754Compatible;
newSettings.TopExample = this.TopExample;
newSettings.EnableUriEscapeFunctionCall = this.EnableUriEscapeFunctionCall;
return newSettings;
}

View file

@ -4,6 +4,7 @@
// ------------------------------------------------------------
using System;
using System.Runtime.CompilerServices;
using Microsoft.OData.Edm;
using Xunit;
@ -95,11 +96,57 @@ namespace Microsoft.OpenApi.OData.Edm.Tests
Assert.Equal(expected, segment.GetPathItemName(settings));
}
private EdmFunction BoundFunction(string funcName, bool isBound, IEdmTypeReference firstParameterType)
[Theory]
[InlineData(true, true, "{param}")]
[InlineData(true, false, "NS.MyFunction(param={param})")]
[InlineData(false, true, "NS.MyFunction(param={param})")]
[InlineData(false, false, "NS.MyFunction(param={param})")]
public void GetPathItemNameReturnsCorrectFunctionLiteralForEscapedFunction(bool isEscapedFunction, bool enableEscapeFunctionCall, string expected)
{
// Arrange & Act
IEdmEntityTypeReference entityTypeReference = new EdmEntityTypeReference(new EdmEntityType("NS", "Entity"), false);
IEdmTypeReference parameterType = EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.String, isNullable: false);
EdmFunction boundFunction = BoundFunction("MyFunction", true, entityTypeReference);
boundFunction.AddParameter("param", parameterType);
var segment = new ODataOperationSegment(boundFunction, isEscapedFunction);
OpenApiConvertSettings settings = new OpenApiConvertSettings
{
EnableUriEscapeFunctionCall = enableEscapeFunctionCall
};
// Assert
Assert.Equal(expected, segment.GetPathItemName(settings));
}
[Theory]
[InlineData(true, true, "{param}:")]
[InlineData(true, false, "NS.MyFunction(param={param})")]
[InlineData(false, true, "NS.MyFunction(param={param})")]
[InlineData(false, false, "NS.MyFunction(param={param})")]
public void GetPathItemNameReturnsCorrectFunctionLiteralForEscapedComposableFunction(bool isEscapedFunction, bool enableEscapeFunctionCall, string expected)
{
// Arrange & Act
IEdmEntityTypeReference entityTypeReference = new EdmEntityTypeReference(new EdmEntityType("NS", "Entity"), false);
IEdmTypeReference parameterType = EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.String, isNullable: false);
EdmFunction boundFunction = BoundFunction("MyFunction", true, entityTypeReference, true);
boundFunction.AddParameter("param", parameterType);
var segment = new ODataOperationSegment(boundFunction, isEscapedFunction);
OpenApiConvertSettings settings = new OpenApiConvertSettings
{
EnableUriEscapeFunctionCall = enableEscapeFunctionCall
};
// Assert
Assert.Equal(expected, segment.GetPathItemName(settings));
}
private EdmFunction BoundFunction(string funcName, bool isBound, IEdmTypeReference firstParameterType, bool isComposable = false)
{
IEdmTypeReference returnType = EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Boolean, isNullable: false);
EdmFunction boundFunction = new EdmFunction("NS", funcName, returnType,
isBound: isBound, entitySetPathExpression: null, isComposable: false);
isBound: isBound, entitySetPathExpression: null, isComposable: isComposable);
boundFunction.AddParameter("entity", firstParameterType);
return boundFunction;
}

View file

@ -5,6 +5,9 @@
using System;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Csdl;
using Microsoft.OData.Edm.Vocabularies;
using Microsoft.OData.Edm.Vocabularies.Community.V1;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Tests;
using Xunit;
@ -64,5 +67,58 @@ namespace Microsoft.OpenApi.OData.Generator.Tests
Assert.Contains("/CountryOrRegion/{Name}", pathItems.Keys);
Assert.Contains("/Me", pathItems.Keys);
}
[Theory]
[InlineData(true, true, true, "/Customers({ID}):/{param}:")]
[InlineData(true, true, false, "/Customers({ID}):/{param}")]
[InlineData(true, false, true, "/Customers({ID})/NS.MyFunction(param={param})")]
[InlineData(true, false, false, "/Customers({ID})/NS.MyFunction(param={param})")]
[InlineData(false, true, true, "/Customers({ID})/NS.MyFunction(param={param})")]
[InlineData(false, true, false, "/Customers({ID})/NS.MyFunction(param={param})")]
[InlineData(false, false, true, "/Customers({ID})/NS.MyFunction(param={param})")]
[InlineData(false, false, false, "/Customers({ID})/NS.MyFunction(param={param})")]
public void CreatePathItemsReturnsForEscapeFunctionModel(bool enableEscaped, bool hasEscapedAnnotation, bool isComposable, string expected)
{
// Arrange
EdmModel model = new EdmModel();
EdmEntityType customer = new EdmEntityType("NS", "Customer");
customer.AddKeys(customer.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32));
model.AddElement(customer);
EdmFunction function = new EdmFunction("NS", "MyFunction", EdmCoreModel.Instance.GetString(false), true, null, isComposable);
function.AddParameter("entity", new EdmEntityTypeReference(customer, false));
function.AddParameter("param", EdmCoreModel.Instance.GetString(false));
model.AddElement(function);
EdmEntityContainer container = new EdmEntityContainer("NS", "Default");
EdmEntitySet customers = new EdmEntitySet(container, "Customers", customer);
container.AddElement(customers);
model.AddElement(container);
if (hasEscapedAnnotation)
{
IEdmBooleanConstantExpression booleanConstant = new EdmBooleanConstant(true);
IEdmTerm term = CommunityVocabularyModel.UrlEscapeFunctionTerm;
EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(function, term, booleanConstant);
annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline);
model.SetVocabularyAnnotation(annotation);
}
OpenApiConvertSettings settings = new OpenApiConvertSettings
{
EnableUriEscapeFunctionCall = enableEscaped
};
ODataContext context = new ODataContext(model, settings);
// Act
var pathItems = context.CreatePathItems();
// Assert
Assert.NotNull(pathItems);
Assert.Equal(3, pathItems.Count);
Assert.Contains("/Customers", pathItems.Keys);
Assert.Contains("/Customers({ID})", pathItems.Keys);
Assert.Contains(expected, pathItems.Keys);
}
}
}