diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs index 751e2600..d5e1f555 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,18 @@ 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 = string.IsNullOrEmpty(pathHash) + ? opSegment.GetPathHash(Context.Settings) + : (pathHash + opSegment.GetPathHash(Context.Settings)).GetHashSHA256().Substring(0, 4); + } + + identifiers.Add(segment.Identifier); + } else { identifiers.Add(segment.Identifier); @@ -109,21 +122,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 4244b225..d1b0c2fc 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,99 @@ 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); + + // Overloaded function 1 + EdmFunction function1 = new("NS", "MyFunction1", EdmCoreModel.Instance.GetString(false), true, null, false); + function1.AddParameter("entity", new EdmEntityTypeReference(customer, false)); + model.AddElement(function1); + + // Overloaded function 1 + EdmFunction function2 = new("NS", "MyFunction1", EdmCoreModel.Instance.GetString(false), true, null, false); + function2.AddParameter("entity", new EdmEntityTypeReference(customer, false)); + function2.AddParameter("param", EdmCoreModel.Instance.GetString(false)); + + model.AddElement(function2); + + // Overloaded function 2 + EdmFunction function3 = new("NS", "MyFunction2", EdmCoreModel.Instance.GetString(false), true, null, false); + function3.AddParameter("entity2", new EdmEntityTypeReference(customer, false)); + model.AddElement(function3); + + // Overloaded function 2 + EdmFunction function4 = new("NS", "MyFunction2", EdmCoreModel.Instance.GetString(false), true, null, false); + function4.AddParameter("entity2", new EdmEntityTypeReference(customer, false)); + function4.AddParameter("param", EdmCoreModel.Instance.GetString(false)); + model.AddElement(function4); + + 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 path1 = new(new ODataNavigationSourceSegment(customers), + new ODataKeySegment(customer), + new ODataOperationSegment(function1), + new ODataOperationSegment(function3)); + + ODataPath path2 = new(new ODataNavigationSourceSegment(customers), + new ODataKeySegment(customer), + new ODataOperationSegment(function1), + new ODataOperationSegment(function4)); + + ODataPath path3 = new(new ODataNavigationSourceSegment(customers), + new ODataKeySegment(customer), + new ODataOperationSegment(function2), + new ODataOperationSegment(function3)); + + ODataPath path4 = new(new ODataNavigationSourceSegment(customers), + new ODataKeySegment(customer), + new ODataOperationSegment(function2), + new ODataOperationSegment(function4)); + + // Act + var operation1 = _operationHandler.CreateOperation(context, path1); + var operation2 = _operationHandler.CreateOperation(context, path2); + var operation3 = _operationHandler.CreateOperation(context, path3); + var operation4 = _operationHandler.CreateOperation(context, path4); + + // Assert + Assert.NotNull(operation1); + Assert.NotNull(operation2); + Assert.NotNull(operation3); + Assert.NotNull(operation4); + + if (enableOperationId) + { + Assert.Equal("Customers.Customer.MyFunction1.MyFunction2-c53d", operation1.OperationId); + Assert.Equal("Customers.Customer.MyFunction1.MyFunction2-4d93", operation2.OperationId); + Assert.Equal("Customers.Customer.MyFunction1.MyFunction2-a2b2", operation3.OperationId); + Assert.Equal("Customers.Customer.MyFunction1.MyFunction2-7bea", operation4.OperationId); + } + else + { + Assert.Null(operation1.OperationId); + Assert.Null(operation2.OperationId); + Assert.Null(operation3.OperationId); + Assert.Null(operation4.OperationId); + } + } + [Theory] [InlineData(true)] [InlineData(false)]