diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs
index 2accb7e..218dc8f 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs
@@ -18,6 +18,25 @@ namespace Microsoft.OpenApi.OData.Edm
///
public static class EdmModelExtensions
{
+ ///
+ /// Determines whether the specified operation is UrlEscape function or not.
+ ///
+ /// The Edm model.
+ /// The specified operation.
+ /// true if the specified operation is UrlEscape function; otherwise, false.
+ 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);
+ }
+
///
/// Determines whether the specified function is UrlEscape function or not.
///
diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs
index fb4d336..3a2a8d1 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataOperationSegment.cs
@@ -22,8 +22,18 @@ namespace Microsoft.OpenApi.OData.Edm
///
/// The operation.
public ODataOperationSegment(IEdmOperation operation)
+ : this(operation, false)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of class.
+ ///
+ /// The operation.
+ public ODataOperationSegment(IEdmOperation operation, bool isEscapedFunction)
{
Operation = operation ?? throw Error.ArgumentNull(nameof(operation));
+ IsEscapedFunction = isEscapedFunction;
}
///
@@ -31,6 +41,11 @@ namespace Microsoft.OpenApi.OData.Edm
///
public IEdmOperation Operation { get; }
+ ///
+ /// Gets the is escaped function.
+ ///
+ public bool IsEscapedFunction { get; }
+
///
public override ODataSegmentKind Kind => ODataSegmentKind.Operation;
@@ -52,6 +67,22 @@ namespace Microsoft.OpenApi.OData.Edm
private string FunctionName(IEdmFunction function, OpenApiConvertSettings settings, HashSet 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)
{
diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPath.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPath.cs
index 954cde6..e99c25a 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPath.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPath.cs
@@ -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);
}
diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
index b9afb22..19986be 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
@@ -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 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 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 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;
}
diff --git a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs
index 9842e5b..a5c27f5 100644
--- a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs
@@ -68,6 +68,11 @@ namespace Microsoft.OpenApi.OData
///
public bool EnableOperationId { get; set; } = true;
+ ///
+ /// Gets/sets a value indicating whether to output the binding function as Uri escape function if applied the UriEscapeFunction term.
+ ///
+ public bool EnableUriEscapeFunctionCall { get; set; } = false;
+
///
/// Gets/sets a value indicating whether to verify the edm model before converter.
///
@@ -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;
}
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs
index 88a3819..198943a 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataOperationSegmentTests.cs
@@ -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;
}
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs
index 2f29650..b163330 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiPathItemGeneratorTests.cs
@@ -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);
+ }
}
}