Skip to content

Commit 115e8f6

Browse files
gavinbarronCopilotMIchaelMainer
authored
fix: TryAddPath exact-match check for duplicate bound operation paths (#809)
* fix: TryAddPath exact-match check for duplicate bound operation paths When a duplicate path is detected in TryAddPath, the existing heuristic checks if the new operation's binding type has derived types and, if so, keeps the first entry assuming it was more specific. This is incorrect when the first entry came from a base type (e.g., directoryObject) and the new entry is the exact match for the entity set (e.g., servicePrincipal). Add an exact-match check: if the operation's binding type matches the entity set type, always replace the existing entry regardless of derived types. This ensures paths like /servicePrincipals/delta() return the correct type (servicePrincipal) instead of the base type (directoryObject). Fixes #808 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: removes outdated compilation files causing failure Reflection of #766 - removes obsolete tool/ build files and UpdateDocs project references that were causing build failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs Co-authored-by: Michael Mainer <8527305+MIchaelMainer@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Michael Mainer <8527305+MIchaelMainer@users.noreply.github.com>
1 parent 052e168 commit 115e8f6

13 files changed

Lines changed: 81 additions & 429 deletions

File tree

Build.props

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,4 @@
1515
<EnlistmentRoot Condition="'$(EnlistmentRoot)' == ''">$(MSBuildThisFileDirectory.TrimEnd('\'))</EnlistmentRoot>
1616
</PropertyGroup>
1717

18-
<Import Project="$(EnlistmentRoot)\tool\Build.props" />
19-
2018
</Project>

Microsoft.OpenApi.OData.sln

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 16
44
VisualStudioVersion = 16.0.30907.101
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.OData.Reader", "src\Microsoft.OpenApi.OData.Reader\Microsoft.OpenAPI.OData.Reader.csproj", "{FF3ACD93-19E0-486C-9C0F-FA1C2E7FC8C2}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenAPI.OData.Reader", "src\Microsoft.OpenApi.OData.Reader\Microsoft.OpenAPI.OData.Reader.csproj", "{FF3ACD93-19E0-486C-9C0F-FA1C2E7FC8C2}"
77
EndProject
8-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.OData.Reader.Tests", "test\Microsoft.OpenAPI.OData.Reader.Tests\Microsoft.OpenAPI.OData.Reader.Tests.csproj", "{90A98718-75EB-4E2B-A51E-66ACF66F15B4}"
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenAPI.OData.Reader.Tests", "test\Microsoft.OpenAPI.OData.Reader.Tests\Microsoft.OpenAPI.OData.Reader.Tests.csproj", "{90A98718-75EB-4E2B-A51E-66ACF66F15B4}"
99
EndProject
1010
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OoasUtil", "src\OoasUtil\OoasUtil.csproj", "{2D06C660-B550-432C-8062-D4070F7C371F}"
1111
EndProject
@@ -16,10 +16,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1616
.editorconfig = .editorconfig
1717
EndProjectSection
1818
EndProject
19-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tool", "tool", "{DE8F8E75-A119-4CF3-AFDD-4132B55DAE76}"
20-
EndProject
21-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateDocs", "tool\UpdateDocs\UpdateDocs.csproj", "{AAC31ECB-05F9-444A-9B86-42ECD50AA468}"
22-
EndProject
2319
Global
2420
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2521
Debug|Any CPU = Debug|Any CPU
@@ -42,18 +38,11 @@ Global
4238
{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
4339
{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
4440
{79B190E8-EDB0-4C03-8FD8-EB48E4807CFB}.Release|Any CPU.Build.0 = Release|Any CPU
45-
{AAC31ECB-05F9-444A-9B86-42ECD50AA468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46-
{AAC31ECB-05F9-444A-9B86-42ECD50AA468}.Debug|Any CPU.Build.0 = Debug|Any CPU
47-
{AAC31ECB-05F9-444A-9B86-42ECD50AA468}.Release|Any CPU.ActiveCfg = Release|Any CPU
48-
{AAC31ECB-05F9-444A-9B86-42ECD50AA468}.Release|Any CPU.Build.0 = Release|Any CPU
4941
EndGlobalSection
5042
GlobalSection(SolutionProperties) = preSolution
5143
HideSolutionNode = FALSE
5244
EndGlobalSection
5345
GlobalSection(ExtensibilityGlobals) = postSolution
5446
SolutionGuid = {9AE22713-F94E-45CA-81F4-0806CA195B69}
5547
EndGlobalSection
56-
GlobalSection(NestedProjects) = preSolution
57-
{AAC31ECB-05F9-444A-9B86-42ECD50AA468} = {DE8F8E75-A119-4CF3-AFDD-4132B55DAE76}
58-
EndGlobalSection
5948
EndGlobal

src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,13 @@ internal static bool TryAddPath(this IDictionary<string, OpenApiPathItem> pathIt
372372

373373
if (derivedTypes?.Any() ?? false)
374374
{
375+
if (boundEntityType != null && boundEntityType == operationEntityType)
376+
{
377+
// The operation's binding type exactly matches the entity set's type,
378+
// so this is a more specific overload than whatever was added first.
379+
pathItems[pathName] = pathItem;
380+
return true;
381+
}
375382
if (boundEntityType != null && !derivedTypes.Contains(boundEntityType))
376383
{
377384
Debug.WriteLine($"Duplicate paths present but entity type of binding parameter '{operationEntityType}' " +

test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.OData.Edm;
1313
using Microsoft.OData.Edm.Csdl;
1414
using Microsoft.OData.Edm.Validation;
15+
using Microsoft.OpenApi.Models;
1516
using Microsoft.OpenApi.OData.Tests;
1617
using Xunit;
1718

@@ -921,6 +922,77 @@ private static IEdmModel GetNavPropModel(string annotation)
921922
return GetEdmModel(template);
922923
}
923924

925+
[Fact]
926+
public void GetPathsForDerivedTypeDeltaFunctionUsesCorrectReturnType()
927+
{
928+
// Arrange – mirrors the Graph scenario:
929+
// directoryObject (base) has delta with RequiresExplicitBinding
930+
// servicePrincipal (derived) has its own delta
931+
// agentIdentity (derived from servicePrincipal) causes servicePrincipal to have derived types
932+
// Bug: TryAddPath kept the base-type delta for /servicePrincipals/delta() because
933+
// servicePrincipal has derived types.
934+
string csdl = @"<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx"">
935+
<edmx:DataServices>
936+
<Schema Namespace=""NS"" xmlns=""http://docs.oasis-open.org/odata/ns/edm"">
937+
<EntityType Name=""directoryObject"">
938+
<Key>
939+
<PropertyRef Name=""id"" />
940+
</Key>
941+
<Property Name=""id"" Type=""Edm.String"" Nullable=""false"" />
942+
</EntityType>
943+
<EntityType Name=""servicePrincipal"" BaseType=""NS.directoryObject"">
944+
<Property Name=""appId"" Type=""Edm.String"" />
945+
</EntityType>
946+
<EntityType Name=""agentIdentity"" BaseType=""NS.servicePrincipal"">
947+
<Property Name=""blueprintId"" Type=""Edm.String"" />
948+
</EntityType>
949+
<Function Name=""delta"" IsBound=""true"">
950+
<Parameter Name=""bindingParameter"" Type=""Collection(NS.directoryObject)"" />
951+
<ReturnType Type=""Collection(NS.directoryObject)"" />
952+
<Annotation Term=""Org.OData.Core.V1.RequiresExplicitBinding"" />
953+
</Function>
954+
<Function Name=""delta"" IsBound=""true"">
955+
<Parameter Name=""bindingParameter"" Type=""Collection(NS.servicePrincipal)"" />
956+
<ReturnType Type=""Collection(NS.servicePrincipal)"" />
957+
</Function>
958+
<EntityContainer Name=""Default"">
959+
<EntitySet Name=""directoryObjects"" EntityType=""NS.directoryObject"" />
960+
<EntitySet Name=""servicePrincipals"" EntityType=""NS.servicePrincipal"" />
961+
</EntityContainer>
962+
<Annotations Target=""NS.directoryObject"">
963+
<Annotation Term=""Org.OData.Core.V1.ExplicitOperationBindings"">
964+
<Collection>
965+
<String>NS.delta</String>
966+
</Collection>
967+
</Annotation>
968+
</Annotations>
969+
</Schema>
970+
</edmx:DataServices>
971+
</edmx:Edmx>";
972+
973+
bool result = CsdlReader.TryParse(XElement.Parse(csdl).CreateReader(), out IEdmModel model, out _);
974+
Assert.True(result);
975+
976+
var settings = new OpenApiConvertSettings();
977+
OpenApiDocument doc = model.ConvertToOpenApi(settings);
978+
979+
// Assert – the /servicePrincipals/NS.delta() path should exist and its response
980+
// should reference NS.servicePrincipal, not NS.directoryObject
981+
Assert.True(doc.Paths.ContainsKey("/servicePrincipals/NS.delta()"),
982+
$"Expected path not found. Available: {string.Join(", ", doc.Paths.Keys)}");
983+
984+
var pathItem = doc.Paths["/servicePrincipals/NS.delta()"];
985+
var getOp = pathItem.Operations[OperationType.Get];
986+
Assert.NotNull(getOp);
987+
988+
// The response schema title should reference servicePrincipal, not directoryObject
989+
var responseKey = getOp.Responses.Keys.First();
990+
var response = getOp.Responses[responseKey];
991+
var schema = response.Content["application/json"].Schema;
992+
Assert.Contains("servicePrincipal", schema.Title);
993+
Assert.DoesNotContain("directoryObject", schema.Title);
994+
}
995+
924996
private static IEdmModel GetEdmModel(string schema)
925997
{
926998
bool parsed = SchemaReader.TryParse(new XmlReader[] { XmlReader.Create(new StringReader(schema)) }, out IEdmModel parsedModel, out IEnumerable<EdmError> errors);

tool/After.Common.targets

Lines changed: 0 additions & 105 deletions
This file was deleted.

tool/Before.Common.targets

Lines changed: 0 additions & 27 deletions
This file was deleted.

tool/Build.props

Lines changed: 0 additions & 30 deletions
This file was deleted.

tool/GetNugetPackageMetadata.proj

Lines changed: 0 additions & 14 deletions
This file was deleted.

tool/PoliCheck/RunPoliCheck.ps1

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)