diff --git a/src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs b/src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs index 099a7d20c..9bbf08f68 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs @@ -163,11 +163,13 @@ internal static IList RetrieveNavigationPropertyPathsOperationIdSegments // For navigation property paths with odata type cast segments // the OData type cast segments identifiers will be used in the operation id + // The same applies for navigation property paths with operation segments. IEnumerable segments = path.Segments.Skip(1) .Where(static s => s is ODataNavigationPropertySegment || s is ODataTypeCastSegment || - s is ODataOperationSegment); + s is ODataOperationSegment || + s is ODataKeySegment); Utils.CheckArgumentNull(segments, nameof(segments)); string previousTypeCastSegmentId = null; @@ -190,6 +192,18 @@ s is ODataTypeCastSegment || // Navigation property generated via composable function items.Add(operationSegment.Identifier); } + else if (segment is ODataKeySegment keySegment && keySegment.IsAlternateKey) + { + // We'll consider alternate keys in the operation id to eliminate potential duplicates with operation id of primary path + if (segment == segments.Last()) + { + items.Add("By" + string.Join("", keySegment.Identifier.Split(',').Select(static x => Utils.UpperFirstChar(x)))); + } + else + { + items.Add(keySegment.Identifier); + } + } } return items; 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 9a80e68fe..d6c6915a7 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj +++ b/src/Microsoft.OpenApi.OData.Reader/Microsoft.OpenAPI.OData.Reader.csproj @@ -15,7 +15,7 @@ netstandard2.0 Microsoft.OpenApi.OData true - 1.6.0-preview.6 + 1.6.0-preview.7 This package contains the codes you need to convert OData CSDL to Open API Document of Model. © Microsoft Corporation. All rights reserved. Microsoft OpenApi OData EDM @@ -28,6 +28,7 @@ - Generates $expand query parameter for operations whose return type is a collection #481 - Adds delete operation for non-contained navigation properties only if explicitly allowed via annotation #483 - Appends parameters and fixes operation ids of navigation property paths generated via composable functions #486 +- Use alternate keys in the generation of operation ids of operations and navigation property alternate paths #488 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 fe5bf6cf0..9e6aaedc0 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EdmOperationOperationHandler.cs @@ -73,16 +73,30 @@ protected override void SetBasicInfo(OpenApiOperation operation) // in the operationId to avoid potential // duplicates in entity vs entityset functions/actions - List identifiers = new(); + List identifiers = []; foreach (ODataSegment segment in Path.Segments) { - if (segment is not ODataKeySegment) + if (segment is ODataKeySegment keySegment) { - identifiers.Add(segment.Identifier); + if (!keySegment.IsAlternateKey) + { + identifiers.Add(segment.EntityType.Name); + continue; + } + + // We'll consider alternate keys in the operation id to eliminate potential duplicates with operation id of primary path + if (segment == Path.Segments.Last()) + { + identifiers.Add("By" + string.Join("", keySegment.Identifier.Split(',').Select(static x => Utils.UpperFirstChar(x)))); + } + else + { + identifiers.Add(keySegment.Identifier); + } } else { - identifiers.Add(segment.EntityType.Name); + identifiers.Add(segment.Identifier); } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs index 21ea17ebe..da7b256a1 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs @@ -52,7 +52,7 @@ public void GetPathsForGraphBetaModelReturnsAllPaths() // Assert Assert.NotNull(paths); - Assert.Equal(16976, paths.Count()); + Assert.Equal(16980, paths.Count()); AssertGraphBetaModelPaths(paths); } @@ -117,7 +117,7 @@ public void GetPathsForGraphBetaModelWithDerivedTypesConstraintReturnsAllPaths() // Assert Assert.NotNull(paths); - Assert.Equal(17627, paths.Count()); + Assert.Equal(17631, paths.Count()); } [Theory] diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs index a3d8baf79..de817a521 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EdmFunctionOperationHandlerTests.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Microsoft.OData.Edm; @@ -479,5 +480,37 @@ public void CreateOperationForFunctionWithDateTimeParametersReturnsCorrectPathIt Assert.Equal("Usage: periodEnd={periodEnd}", operation.Parameters.First(x => x.Name == "periodEnd").Description); } + + [Fact] + public void CreateFunctionOperationWithAlternateKeyReturnsCorrectOperationId() + { + // Arrange + IEdmModel model = EdmModelHelper.GraphBetaModel; + ODataContext context = new(model, new OpenApiConvertSettings() + { + EnableOperationId = true + }); + + IEdmSingleton singleton = model.EntityContainer.FindSingleton("communications"); + IEdmEntityType entityType = model.SchemaElements.OfType().First(c => c.Name == "cloudCommunications"); + IEdmNavigationProperty navProp = entityType.DeclaredNavigationProperties().First(c => c.Name == "onlineMeetings"); + IEdmOperation action = model.SchemaElements.OfType().First(f => f.Name == "sendVirtualAppointmentReminderSms"); + IDictionary keyMappings = new Dictionary { { "joinWebUrl", "joinWebUrl" } }; + + ODataPath path = new(new ODataNavigationSourceSegment(singleton), + new ODataNavigationPropertySegment(navProp), + new ODataKeySegment(entityType, keyMappings) + { + IsAlternateKey = true + }, + new ODataOperationSegment(action)); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("communications.onlineMeetings.joinWebUrl.sendVirtualAppointmentReminderSms", operation.OperationId); + } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyGetOperationHandlerTests.cs index b1133a6f9..57f565eb5 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyGetOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/NavigationPropertyGetOperationHandlerTests.cs @@ -1,8 +1,9 @@ -// ------------------------------------------------------------ +// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using System.Collections.Generic; using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Extensions; @@ -242,5 +243,35 @@ public void CreateNavigationGetOperationReturnsSecurityForReadRestrictions(bool Assert.Empty(operation.Security); } } + + [Fact] + public void CreateNavigationGetOperationWithAlternateKeyReturnsCorrectOperationId() + { + // Arrange + IEdmModel model = EdmModelHelper.GraphBetaModel; + ODataContext context = new(model, new OpenApiConvertSettings() + { + EnableOperationId = true + }); + + IEdmSingleton singleton = model.EntityContainer.FindSingleton("communications"); + IEdmEntityType entityType = model.SchemaElements.OfType().First(c => c.Name == "cloudCommunications"); + IEdmNavigationProperty navProp = entityType.DeclaredNavigationProperties().First(c => c.Name == "onlineMeetings"); + IDictionary keyMappings = new Dictionary { { "joinWebUrl", "joinWebUrl" } }; + + ODataPath path = new(new ODataNavigationSourceSegment(singleton), + new ODataNavigationPropertySegment(navProp), + new ODataKeySegment(entityType, keyMappings) + { + IsAlternateKey = true + }); + + // Act + var operation = _operationHandler.CreateOperation(context, path); + + // Assert + Assert.NotNull(operation); + Assert.Equal("communications.onlineMeetings.GetByJoinWebUrl", operation.OperationId); + } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml index ebf03e5f5..9c941dd43 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Resources/Graph.Beta.OData.xml @@ -102459,6 +102459,11 @@ + + + + + @@ -132565,6 +132570,10 @@ + + + + @@ -133020,6 +133029,10 @@ + + + +