diff --git a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj
index ff7dd0b7..e61a7d0f 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj
+++ b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj
@@ -27,6 +27,7 @@
- Adds support for retrieving collection of enum values from UpdateMethod property of UpdateRestrictions annotation #564
- Adds nullable to double schema conversions #581
- Updates tag names for actions/functions operations #585
+ - Creates unique operation ids for paths with composable overloaded functions #580
Microsoft.OpenApi.OData.Reader
..\..\tool\Microsoft.OpenApi.OData.snk
diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs
index 55ad88b2..ad6217e9 100644
--- a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs
+++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs
@@ -81,6 +81,7 @@ protected override void SetBasicInfo(OpenApiOperation operation)
// duplicates in entity vs entityset functions/actions
List identifiers = new();
+ string pathHash = string.Empty;
foreach (ODataSegment segment in Path.Segments)
{
if (segment is ODataKeySegment keySegment)
@@ -101,6 +102,16 @@ protected override void SetBasicInfo(OpenApiOperation operation)
identifiers.Add(keySegment.Identifier);
}
}
+ else if (segment is ODataOperationSegment opSegment)
+ {
+ if (opSegment.Operation is IEdmFunction function && Context.Model.IsOperationOverload(function))
+ {
+ // Hash the segment to avoid duplicate operationIds
+ pathHash = segment.GetPathHash(Context.Settings);
+ }
+
+ identifiers.Add(segment.Identifier);
+ }
else
{
identifiers.Add(segment.Identifier);
@@ -109,21 +120,13 @@ protected override void SetBasicInfo(OpenApiOperation operation)
string operationId = string.Join(".", identifiers);
- if (EdmOperation.IsAction())
+ if (!string.IsNullOrEmpty(pathHash))
{
- operation.OperationId = operationId;
+ operation.OperationId = operationId + "-" + pathHash;
}
else
{
- if (Path.LastSegment is ODataOperationSegment operationSegment &&
- Context.Model.IsOperationOverload(operationSegment.Operation))
- {
- operation.OperationId = operationId + "-" + Path.LastSegment.GetPathHash(Context.Settings);
- }
- else
- {
- operation.OperationId = operationId;
- }
+ operation.OperationId = operationId;
}
}
diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs
index 49322066..5f225a81 100644
--- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs
+++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs
@@ -1,4 +1,4 @@
-// ------------------------------------------------------------
+// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
@@ -299,6 +299,65 @@ public void CreateOperationForOverloadEdmFunctionReturnsCorrectOperationId(bool
}
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void CreateOperationForComposableOverloadEdmFunctionReturnsCorrectOperationId(bool enableOperationId)
+ {
+ // Arrange
+ EdmModel model = new();
+ EdmEntityType customer = new("NS", "Customer");
+ customer.AddKeys(customer.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32));
+ model.AddElement(customer);
+
+ EdmFunction function = new("NS", "MyFunction", EdmCoreModel.Instance.GetString(false), true, null, false);
+ function.AddParameter("entity", new EdmEntityTypeReference(customer, false));
+ function.AddParameter("param", EdmCoreModel.Instance.GetString(false));
+ model.AddElement(function);
+
+ function = new EdmFunction("NS", "MyFunction", EdmCoreModel.Instance.GetString(false), true, null, false);
+ function.AddParameter("entity", new EdmEntityTypeReference(customer, false));
+ function.AddParameter("param", EdmCoreModel.Instance.GetString(false));
+ function.AddParameter("param2", EdmCoreModel.Instance.GetString(false));
+ model.AddElement(function);
+
+ EdmFunction function2 = new("NS", "MyFunction2", EdmCoreModel.Instance.GetString(false), true, null, false);
+ function2.AddParameter("entity2", new EdmEntityTypeReference(customer, false));
+ function2.AddParameter("param3", EdmCoreModel.Instance.GetString(false));
+ model.AddElement(function2);
+
+ EdmEntityContainer container = new("NS", "Default");
+ EdmEntitySet customers = new(container, "Customers", customer);
+ model.AddElement(container);
+
+ OpenApiConvertSettings settings = new OpenApiConvertSettings
+ {
+ EnableOperationId = enableOperationId,
+ AddSingleQuotesForStringParameters = true,
+ };
+ ODataContext context = new(model, settings);
+
+ ODataPath path = new(new ODataNavigationSourceSegment(customers),
+ new ODataKeySegment(customer),
+ new ODataOperationSegment(function),
+ new ODataOperationSegment(function2));
+
+ // Act
+ var operation = _operationHandler.CreateOperation(context, path);
+
+ // Assert
+ Assert.NotNull(operation);
+
+ if (enableOperationId)
+ {
+ Assert.Equal("Customers.Customer.MyFunction.MyFunction2-df74", operation.OperationId);
+ }
+ else
+ {
+ Assert.Null(operation.OperationId);
+ }
+ }
+
[Theory]
[InlineData(true)]
[InlineData(false)]