From 3fe7614972df0b693875e19d498e2fb8d1f79580 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 17 Apr 2024 18:16:10 +0300 Subject: [PATCH 1/5] Register and retrieve JsonSchema $refs with $ids specified --- .../Models/OpenApiDocument.cs | 24 +++++++++---------- .../OpenApiComponentsRegistryExtensions.cs | 17 ++++++++++++- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 28ed47325..6bef375e5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -485,22 +485,22 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) /// /// A JsonSchema ref. public JsonSchema ResolveJsonSchemaReference(Uri referenceUri) - { + { + const char pound = '#'; string uriLocation; - string id = referenceUri.OriginalString.Split('/')?.Last(); - string relativePath = "/components/" + ReferenceType.Schema.GetDisplayName() + "/" + id; - - if (referenceUri.OriginalString.StartsWith("#")) + int poundIndex = referenceUri.OriginalString.IndexOf(pound); + + if (poundIndex > 0) { - // Local reference - uriLocation = BaseUri + relativePath; + // External reference, ex: ./TodoReference.yaml#/components/schemas/todo + string externalUri = referenceUri.OriginalString.Split(pound).First(); + Uri externalDocId = Workspace.GetDocumentId(externalUri); + string relativePath = referenceUri.OriginalString.Split(pound).Last(); + uriLocation = externalDocId + relativePath; } else { - // External reference - var externalUri = referenceUri.OriginalString.Split('#').First(); - var externalDocId = Workspace.GetDocumentId(externalUri); - uriLocation = externalDocId + relativePath; + uriLocation = BaseUri + referenceUri.ToString().TrimStart(pound); } return (JsonSchema)Workspace.ResolveReference(uriLocation); diff --git a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs index 9f129c016..6d33d0022 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using Json.Schema; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -17,7 +18,21 @@ public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDo // Register Schema foreach (var item in document.Components.Schemas) { - var location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; + string location; + + if (item.Value.GetId() != null) + { + location = document.BaseUri + item.Value.GetId().ToString(); + } + else if (item.Value.GetRef() != null) + { + location = document.BaseUri + item.Value.GetRef().ToString().TrimStart('#'); + } + else + { + location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; + } + workspace.RegisterComponent(location, item.Value); } From 70433e4cadf02e35b8784e8f1a5cd6175fc3dcb8 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 18 Apr 2024 21:28:26 +0300 Subject: [PATCH 2/5] Update csproj property for CopyToOutputDirectory --- .../Microsoft.OpenApi.Readers.Tests.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 63e28a8a9..935819ce9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 false @@ -7,10 +7,10 @@ - Always + PreserveNewest - Always + PreserveNewest From 3ab98342614c32e565322e8b84ef1f0ff530e5cf Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 22 Apr 2024 21:02:33 +0300 Subject: [PATCH 3/5] Use OpenApiSpecVersion when registering components --- .../Reader/Services/OpenApiWorkspaceLoader.cs | 7 ++++--- .../Reader/V2/OpenApiDocumentDeserializer.cs | 2 +- .../Reader/V3/OpenApiDocumentDeserializer.cs | 2 +- .../Reader/V31/OpenApiDocumentDeserializer.cs | 2 +- .../OpenApiComponentsRegistryExtensions.cs | 15 +++++++++------ .../References/OpenApiPathItemReferenceTests.cs | 4 ++-- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs index abed56b2c..6915d60bd 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Interfaces; @@ -27,7 +27,8 @@ internal async Task LoadAsync(OpenApiReference reference, CancellationToken cancellationToken = default) { _workspace.AddDocumentId(reference.ExternalResource, document.BaseUri); - _workspace.RegisterComponents(document); + var version = diagnostic?.SpecificationVersion ?? OpenApiSpecVersion.OpenApi3_0; + _workspace.RegisterComponents(document, version); document.Workspace = _workspace; // Collect remote references by walking document @@ -35,7 +36,7 @@ internal async Task LoadAsync(OpenApiReference reference, var collectorWalker = new OpenApiWalker(referenceCollector); collectorWalker.Walk(document); - diagnostic ??= new(); + diagnostic ??= new() { SpecificationVersion = version }; // Walk references foreach (var item in referenceCollector.References) diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs index 0f814616f..616ccf214 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs @@ -253,7 +253,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) FixRequestBodyReferences(openApiDoc); // Register components - openApiDoc.Workspace.RegisterComponents(openApiDoc); + openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi2_0); return openApiDoc; } diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs index 3ed838de9..e3614555f 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs @@ -54,7 +54,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); // Register components - openApiDoc.Workspace.RegisterComponents(openApiDoc); + openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi3_0); return openApiDoc; } diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs index e4de78613..f22900151 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs @@ -53,7 +53,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); // Register components - openApiDoc.Workspace.RegisterComponents(openApiDoc); + openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi3_1); return openApiDoc; } diff --git a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs index 6d33d0022..376135de2 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs @@ -9,7 +9,7 @@ namespace Microsoft.OpenApi.Services { internal static class OpenApiComponentsRegistryExtensions { - public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDocument document) + public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDocument document, OpenApiSpecVersion version = OpenApiSpecVersion.OpenApi3_0) { if (document?.Components == null) return; @@ -24,13 +24,16 @@ public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDo { location = document.BaseUri + item.Value.GetId().ToString(); } - else if (item.Value.GetRef() != null) - { - location = document.BaseUri + item.Value.GetRef().ToString().TrimStart('#'); - } else { - location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; + if (version == OpenApiSpecVersion.OpenApi2_0) + { + location = document.BaseUri + "/" + OpenApiConstants.Definitions + "/" + item.Key; + } + else + { + location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; + } } workspace.RegisterComponent(location, item.Value); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs index 2d7354f78..ec532bed7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs @@ -83,8 +83,8 @@ public OpenApiPathItemReferenceTests() _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); - _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); - _openApiDoc_2.Workspace.RegisterComponents(_openApiDoc_2); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2, OpenApiSpecVersion.OpenApi3_1); + _openApiDoc_2.Workspace.RegisterComponents(_openApiDoc_2, OpenApiSpecVersion.OpenApi3_1); _localPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc_2) { From ac6f717adf7baf19617efa868ed97c609e30babb Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 23 Apr 2024 12:03:56 +0300 Subject: [PATCH 4/5] Use ternary operator; move value to constant --- .../Models/OpenApiConstants.cs | 5 +++ .../Models/OpenApiDocument.cs | 2 +- .../OpenApiComponentsRegistryExtensions.cs | 34 ++++++++----------- .../PublicApi/PublicApi.approved.txt | 1 + 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 5dcf17f7a..90d5c545b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -635,6 +635,11 @@ public static class OpenApiConstants /// public const string BaseRegistryUri = "https://openapi.net/"; + /// + /// The components path segment in a $ref value. + /// + public const string ComponentsSegment = "/components/"; + #region V2.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 76cb76e15..847e72b24 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -569,7 +569,7 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool } string uriLocation; - string relativePath = "/components/" + reference.Type.GetDisplayName() + "/" + reference.Id; + string relativePath = OpenApiConstants.ComponentsSegment + reference.Type.GetDisplayName() + "/" + reference.Id; uriLocation = useExternal ? Workspace.GetDocumentId(reference.ExternalResource)?.OriginalString + relativePath diff --git a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs index 376135de2..2a38c360d 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs @@ -13,27 +13,21 @@ public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDo { if (document?.Components == null) return; - var baseUri = document.BaseUri + "/components/"; + string baseUri = document.BaseUri + OpenApiConstants.ComponentsSegment; + string location; // Register Schema foreach (var item in document.Components.Schemas) { - string location; - if (item.Value.GetId() != null) { location = document.BaseUri + item.Value.GetId().ToString(); } else { - if (version == OpenApiSpecVersion.OpenApi2_0) - { - location = document.BaseUri + "/" + OpenApiConstants.Definitions + "/" + item.Key; - } - else - { - location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; - } + location = version == OpenApiSpecVersion.OpenApi2_0 + ? document.BaseUri + "/" + OpenApiConstants.Definitions + "/" + item.Key + : baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; } workspace.RegisterComponent(location, item.Value); @@ -42,63 +36,63 @@ public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDo // Register Parameters foreach (var item in document.Components.Parameters) { - var location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } // Register Responses foreach (var item in document.Components.Responses) { - var location = baseUri + ReferenceType.Response.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.Response.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } // Register RequestBodies foreach (var item in document.Components.RequestBodies) { - var location = baseUri + ReferenceType.RequestBody.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.RequestBody.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } // Register Links foreach (var item in document.Components.Links) { - var location = baseUri + ReferenceType.Link.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.Link.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } // Register Callbacks foreach (var item in document.Components.Callbacks) { - var location = baseUri + ReferenceType.Callback.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.Callback.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } // Register PathItems foreach (var item in document.Components.PathItems) { - var location = baseUri + ReferenceType.PathItem.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.PathItem.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } // Register Examples foreach (var item in document.Components.Examples) { - var location = baseUri + ReferenceType.Example.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.Example.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } // Register Headers foreach (var item in document.Components.Headers) { - var location = baseUri + ReferenceType.Header.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.Header.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } // Register SecuritySchemes foreach (var item in document.Components.SecuritySchemes) { - var location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key; + location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key; workspace.RegisterComponent(location, item.Value); } } diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 15068d24b..6873cec8f 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -459,6 +459,7 @@ namespace Microsoft.OpenApi.Models public const string Callbacks = "callbacks"; public const string ClientCredentials = "clientCredentials"; public const string Components = "components"; + public const string ComponentsSegment = "/components/"; public const string Consumes = "consumes"; public const string Contact = "contact"; public const string Content = "content"; From cbffc8679ca99dcc304d3e93e5f2a38273a9c0db Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Fri, 26 Apr 2024 14:53:30 +0300 Subject: [PATCH 5/5] Fix build --- .../Microsoft.OpenApi.Readers.Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 001b2180e..7660774c1 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -6,10 +6,10 @@ ..\..\src\Microsoft.OpenApi.snk - + PreserveNewest - + PreserveNewest