From ce5c7245e38b2e38e5f9a7a4eb825e7683fbb1b5 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 5 Mar 2024 15:23:33 +0300 Subject: [PATCH 01/34] Add methods to set and retrieve components registries --- .../Services/OpenApiWorkspace.cs | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index 63c1defaf..73cf9c74f 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.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; @@ -21,6 +21,9 @@ public class OpenApiWorkspace private readonly Dictionary _fragments = new(); private readonly Dictionary _schemaFragments = new(); private readonly Dictionary _artifacts = new(); + private IDictionary _referenceableRegistry = new Dictionary(); + private IDictionary _schemaRegistry = new Dictionary(); + /// /// A list of OpenApiDocuments contained in the workspace @@ -70,6 +73,85 @@ public OpenApiWorkspace() /// public OpenApiWorkspace(OpenApiWorkspace workspace) { } + /// + /// + /// + /// + /// + public void RegisterComponent(Uri uri, IBaseDocument baseDocument) + { + // If reference type is schema, register in IBaseDocument registry + if (uri == null) throw new ArgumentNullException(nameof(uri)); + if (baseDocument == null) throw new ArgumentNullException(nameof(baseDocument)); + + if (_schemaRegistry.ContainsKey(uri.ToString())) + { + throw new InvalidOperationException($"Key already exists. {nameof(uri)} needs to be unique"); + } + else + { + _schemaRegistry.Add(uri.ToString(), baseDocument); + } + } + + /// + /// + /// + /// + /// + public void RegisterComponent(Uri uri, IOpenApiReferenceable referenceable) + { + if (uri == null) throw new ArgumentNullException(nameof(uri)); + if (referenceable == null) throw new ArgumentNullException(nameof(referenceable)); + + if (_schemaRegistry.ContainsKey(uri.OriginalString)) + { + throw new InvalidOperationException($"Key already exists. {nameof(uri)} needs to be unique"); + } + else + { + _referenceableRegistry.Add(uri.OriginalString, referenceable); + } + } + + /// + /// + /// + /// + /// + /// + /// + public bool TryRetrieveComponent(Uri uri, out TValue value) + { + if (uri == null) + { + value = default; + return false; + } + + if ((typeof(TValue) == typeof(IBaseDocument))) + { + _schemaRegistry.TryGetValue(uri.OriginalString, out IBaseDocument schema); + if (schema != null) + { + value = (TValue)schema; + return true; + } + } + else if(typeof(TValue) == typeof(IOpenApiReferenceable)) + { + _referenceableRegistry.TryGetValue(uri.OriginalString, out IOpenApiReferenceable referenceable); + if (referenceable != null) + { + value = (TValue)referenceable; + return true; + } + } + + value = default; + return false; + } + /// /// Verify if workspace contains a document based on its URL. /// From 94028aebd58ea08566c7edd2df31d41c6e343658 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 5 Mar 2024 15:24:14 +0300 Subject: [PATCH 02/34] Create new property that will capture unique document ID or base Uri --- src/Microsoft.OpenApi/Models/OpenApiDocument.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index f0c341f48..6563372ff 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -23,7 +23,7 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo /// /// Related workspace containing OpenApiDocuments that are referenced in this document /// - public OpenApiWorkspace Workspace { get; set; } + public OpenApiWorkspace Workspace { get; set; } = new(); /// /// REQUIRED. Provides metadata about the API. The metadata MAY be used by tooling as required. @@ -89,10 +89,21 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo public Uri BaseUri { get; } /// - /// Parameter-less constructor + /// /// - public OpenApiDocument() { } + public string DocumentID { get; } + /// + /// Parameter-less constructor + /// + public OpenApiDocument() + { + var documentId = (Servers.FirstOrDefault()?.Url.ToString()) + ?? "http://openapi.net/" + HashCode; + DocumentID = documentId; + Workspace.AddDocument(documentId, this); + } + /// /// Initializes a copy of an an object /// From e27a4e884c8b2f647e27f24d1391a7def1cd8254 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 18 Mar 2024 11:32:57 +0300 Subject: [PATCH 03/34] Update workspace --- src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index 73cf9c74f..72c5cb030 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.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; @@ -80,7 +80,6 @@ public OpenApiWorkspace(OpenApiWorkspace workspace) { } /// public void RegisterComponent(Uri uri, IBaseDocument baseDocument) { - // If reference type is schema, register in IBaseDocument registry if (uri == null) throw new ArgumentNullException(nameof(uri)); if (baseDocument == null) throw new ArgumentNullException(nameof(baseDocument)); @@ -90,7 +89,7 @@ public void RegisterComponent(Uri uri, IBaseDocument baseDocument) } else { - _schemaRegistry.Add(uri.ToString(), baseDocument); + _schemaRegistry.Add(uri.OriginalString, baseDocument); } } @@ -104,7 +103,7 @@ public void RegisterComponent(Uri uri, IOpenApiReferenceable referenceable) if (uri == null) throw new ArgumentNullException(nameof(uri)); if (referenceable == null) throw new ArgumentNullException(nameof(referenceable)); - if (_schemaRegistry.ContainsKey(uri.OriginalString)) + if (_referenceableRegistry.ContainsKey(uri.OriginalString)) { throw new InvalidOperationException($"Key already exists. {nameof(uri)} needs to be unique"); } From 95710e5fcbe913c7290c86d9bad76a4e30bf688d Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 25 Mar 2024 20:40:33 +0300 Subject: [PATCH 04/34] Update reference resolver tests --- .../References/OpenApiCallbackReference.cs | 2 +- .../OpenApiCallbackReferenceTests.cs | 74 ++++++++++-------- .../OpenApiExampleReferenceTests.cs | 78 ++++++++++++------- .../References/OpenApiHeaderReferenceTests.cs | 46 +++++++---- .../References/OpenApiLinkReferenceTests.cs | 46 +++++++---- .../OpenApiParameterReferenceTests.cs | 45 +++++++---- .../OpenApiPathItemReferenceTests.cs | 51 +++++++----- .../OpenApiRequestBodyReferenceTests.cs | 68 ++++++++-------- .../OpenApiResponseReferenceTest.cs | 47 ++++++----- .../OpenApiSecuritySchemeReferenceTests.cs | 3 +- 10 files changed, 287 insertions(+), 173 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs index 33c76d1c2..1f4dbda25 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs @@ -34,7 +34,7 @@ private OpenApiCallback Target /// The host OpenAPI document. /// Optional: External resource in the reference. /// It may be: - /// 1. a absolute/relative file path, for example: ../commons/pet.json + /// 1. an absolute/relative file path, for example: ../commons/pet.json /// 2. a Url, for example: http://localhost/pet.json /// public OpenApiCallbackReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null) diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs index c2fd2b9db..1f06a1ea9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs @@ -18,11 +18,14 @@ namespace Microsoft.OpenApi.Tests.Models.References [UsesVerify] public class OpenApiCallbackReferenceTests { + // OpenApi doc with external $ref private const string OpenApi = @" openapi: 3.0.0 info: title: Callback with ref Example version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 paths: /register: post: @@ -57,33 +60,16 @@ public class OpenApiCallbackReferenceTests example: 2531329f-fb09-4ef7-887e-84e648214436 callbacks: myEvent: - $ref: '#/components/callbacks/callbackEvent' -components: - callbacks: - callbackEvent: - '{$request.body#/callbackUrl}': - post: - requestBody: # Contents of the callback message - required: true - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: Some event happened - required: - - message - responses: - '200': - description: ok"; + $ref: 'https://myserver.com/beta#/components/callbacks/callbackEvent'"; + // OpenApi doc with local $ref private const string OpenApi_2 = @" openapi: 3.0.0 info: title: Callback with ref Example version: 1.0.0 +servers: + - url: https://myserver.com/beta paths: /register: post: @@ -119,30 +105,56 @@ public class OpenApiCallbackReferenceTests callbacks: myEvent: $ref: '#/components/callbacks/callbackEvent' -"; - private readonly OpenApiCallbackReference _localCallbackReference; +components: + callbacks: + callbackEvent: + '{$request.body#/callbackUrl}': + post: + requestBody: # Contents of the callback message + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Some event happened + required: + - message + responses: + '200': + description: ok"; + private readonly OpenApiCallbackReference _externalCallbackReference; + private readonly OpenApiCallbackReference _localCallbackReference; public OpenApiCallbackReferenceTests() { var reader = new OpenApiStringReader(); OpenApiDocument openApiDoc = reader.Read(OpenApi, out _); OpenApiDocument openApiDoc_2 = reader.Read(OpenApi_2, out _); - openApiDoc_2.Workspace = new(); - openApiDoc_2.Workspace.AddDocument("http://localhost/callbackreference", openApiDoc); - _localCallbackReference = new("callbackEvent", openApiDoc); - _externalCallbackReference = new("callbackEvent", openApiDoc_2, "http://localhost/callbackreference"); + openApiDoc.Workspace.AddDocument(openApiDoc_2); + _externalCallbackReference = new("callbackEvent", openApiDoc, "https://myserver.com/beta"); + _localCallbackReference = new("callbackEvent", openApiDoc_2); } [Fact] public void CallbackReferenceResolutionWorks() { // Assert - Assert.NotEmpty(_localCallbackReference.PathItems); + // External reference resolution works Assert.NotEmpty(_externalCallbackReference.PathItems); - Assert.Equal("{$request.body#/callbackUrl}", _localCallbackReference.PathItems.First().Key.Expression); + Assert.Single(_externalCallbackReference.PathItems); Assert.Equal("{$request.body#/callbackUrl}", _externalCallbackReference.PathItems.First().Key.Expression); + Assert.Equal(OperationType.Post, _externalCallbackReference.PathItems.FirstOrDefault().Value.Operations.FirstOrDefault().Key);; + + // Local reference resolution works + Assert.NotEmpty(_localCallbackReference.PathItems); + Assert.Single(_localCallbackReference.PathItems); + Assert.Equal("{$request.body#/callbackUrl}", _localCallbackReference.PathItems.First().Key.Expression); + Assert.Equal(OperationType.Post, _localCallbackReference.PathItems.FirstOrDefault().Value.Operations.FirstOrDefault().Key); ; } [Theory] @@ -155,7 +167,7 @@ public async Task SerializeCallbackReferenceAsV3JsonWorks(bool produceTerseOutpu var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - _localCallbackReference.SerializeAsV3(writer); + _externalCallbackReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -172,7 +184,7 @@ public async Task SerializeCallbackReferenceAsV31JsonWorks(bool produceTerseOutp var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); // Act - _localCallbackReference.SerializeAsV31(writer); + _externalCallbackReference.SerializeAsV31(writer); writer.Flush(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs index 5ef061cbb..786d426af 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs @@ -18,11 +18,14 @@ namespace Microsoft.OpenApi.Tests.Models.References [UsesVerify] public class OpenApiExampleReferenceTests { + // OpenApi doc with external $ref private const string OpenApi = @" openapi: 3.0.0 info: title: Sample API version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 paths: /users: get: @@ -35,32 +38,39 @@ public class OpenApiExampleReferenceTests schema: type: array items: - $ref: '#/components/schemas/User' + $ref: 'https://myserver.com/beta#/components/schemas/User' examples: - - $ref: '#/components/examples/UserExample' + - $ref: 'https://myserver.com/beta#/components/examples/UserExample' components: - schemas: - User: - type: object - properties: - id: - type: integer - name: - type: string - examples: - UserExample: - summary: Example of a user - description: This is is an example of a user - value: - - id: 1 - name: John Doe + callbacks: + callbackEvent: + '{$request.body#/callbackUrl}': + post: + requestBody: # Contents of the callback message + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: Some event happened + required: + - message + responses: + '200': + description: ok""; "; + // OpenApi doc with local $ref private const string OpenApi_2 = @" openapi: 3.0.0 info: title: Sample API version: 1.0.0 +servers: + - url: https://myserver.com/beta paths: /users: get: @@ -76,6 +86,22 @@ public class OpenApiExampleReferenceTests $ref: '#/components/schemas/User' examples: - $ref: '#/components/examples/UserExample' +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string + examples: + UserExample: + summary: Example of a user + description: This is is an example of a user + value: + - id: 1 + name: John Doe "; private readonly OpenApiExampleReference _localExampleReference; @@ -88,16 +114,15 @@ public OpenApiExampleReferenceTests() var reader = new OpenApiStringReader(); _openApiDoc = reader.Read(OpenApi, out _); _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/examplereference", _openApiDoc); + _openApiDoc.Workspace.AddDocument(_openApiDoc_2); - _localExampleReference = new OpenApiExampleReference("UserExample", _openApiDoc) + _localExampleReference = new OpenApiExampleReference("UserExample", _openApiDoc_2) { Summary = "Example of a local user", Description = "This is an example of a local user" }; - _externalExampleReference = new OpenApiExampleReference("UserExample", _openApiDoc_2, "http://localhost/examplereference") + _externalExampleReference = new OpenApiExampleReference("UserExample", _openApiDoc, "https://myserver.com/beta") { Summary = "Example of an external user", Description = "This is an example of an external user" @@ -108,18 +133,19 @@ public OpenApiExampleReferenceTests() public void ExampleReferenceResolutionWorks() { // Assert + Assert.NotNull(_localExampleReference.Value); + Assert.Equal("[{\"id\":1,\"name\":\"John Doe\"}]", _localExampleReference.Value.Node.ToJsonString()); Assert.Equal("Example of a local user", _localExampleReference.Summary); Assert.Equal("This is an example of a local user", _localExampleReference.Description); - Assert.NotNull(_localExampleReference.Value); - Assert.Equal("Example of an external user", _externalExampleReference.Summary); - Assert.Equal("This is an example of an external user", _externalExampleReference.Description); Assert.NotNull(_externalExampleReference.Value); + Assert.Equal("Example of an external user", _externalExampleReference.Summary); + Assert.Equal("This is an example of an external user", _externalExampleReference.Description); // The main description and summary values shouldn't change - Assert.Equal("Example of a user", _openApiDoc.Components.Examples.First().Value.Summary); + Assert.Equal("Example of a user", _openApiDoc_2.Components.Examples.First().Value.Summary); Assert.Equal("This is is an example of a user", - _openApiDoc.Components.Examples.First().Value.Description); + _openApiDoc_2.Components.Examples.FirstOrDefault().Value.Description); } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs index 3ab1895d1..9a2cae2c0 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Readers; @@ -18,11 +19,14 @@ namespace Microsoft.OpenApi.Tests.Models.References [UsesVerify] public class OpenApiHeaderReferenceTests { + // OpenApi doc with external $ref private const string OpenApi= @" openapi: 3.0.0 info: title: Sample API version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 paths: /users: post: @@ -32,20 +36,26 @@ public class OpenApiHeaderReferenceTests description: Post created successfully headers: Location: - $ref: '#/components/headers/LocationHeader' + $ref: 'https://myserver.com/beta##/components/headers/LocationHeader' components: - headers: - LocationHeader: - description: The URL of the newly created post - schema: - type: string + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string "; + // OpenApi doc with local $ref private const string OpenApi_2 = @" openapi: 3.0.0 info: title: Sample API version: 1.0.0 +servers: + - url: https://myserver.com/beta paths: /users: post: @@ -56,6 +66,12 @@ public class OpenApiHeaderReferenceTests headers: Location: $ref: '#/components/headers/LocationHeader' +components: + headers: + LocationHeader: + description: The URL of the newly created post + schema: + type: string "; private readonly OpenApiHeaderReference _localHeaderReference; @@ -68,17 +84,16 @@ public OpenApiHeaderReferenceTests() var reader = new OpenApiStringReader(); _openApiDoc = reader.Read(OpenApi, out _); _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/headerreference", _openApiDoc); + _openApiDoc.Workspace.AddDocument( _openApiDoc_2); - _localHeaderReference = new OpenApiHeaderReference("LocationHeader", _openApiDoc) + _localHeaderReference = new OpenApiHeaderReference("LocationHeader", _openApiDoc_2) { - Description = "Location of the locally created post" + Description = "Location of the locally referenced post" }; - _externalHeaderReference = new OpenApiHeaderReference("LocationHeader", _openApiDoc_2, "http://localhost/headerreference") + _externalHeaderReference = new OpenApiHeaderReference("LocationHeader", _openApiDoc, "https://myserver.com/beta") { - Description = "Location of the external created post" + Description = "Location of the externally referenced post" }; } @@ -86,10 +101,11 @@ public OpenApiHeaderReferenceTests() public void HeaderReferenceResolutionWorks() { // Assert - Assert.Equal("Location of the locally created post", _localHeaderReference.Description); - Assert.Equal("Location of the external created post", _externalHeaderReference.Description); + Assert.Equal(SchemaValueType.String, _externalHeaderReference.Schema.GetJsonType()); + Assert.Equal("Location of the locally referenced post", _localHeaderReference.Description); + Assert.Equal("Location of the externally referenced post", _externalHeaderReference.Description); Assert.Equal("The URL of the newly created post", - _openApiDoc.Components.Headers.First().Value.Description); // The main description value shouldn't change + _openApiDoc_2.Components.Headers.First().Value.Description); // The main description value shouldn't change } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs index ccd4d3de6..2ae849bb8 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs @@ -18,11 +18,14 @@ namespace Microsoft.OpenApi.Tests.Models.References [UsesVerify] public class OpenApiLinkReferenceTests { + // OpenApi doc with external $ref private const string OpenApi = @" openapi: 3.0.0 info: version: 0.0.0 title: Links example +servers: + - url: https://myserver.com/v1.0 paths: /users: post: @@ -49,20 +52,26 @@ public class OpenApiLinkReferenceTests description: ID of the created user. links: GetUserByUserId: - $ref: '#/components/links/GetUserByUserId' # <---- referencing the link here + $ref: 'https://myserver.com/beta#/components/links/GetUserByUserId' # <---- referencing the link here (externally) components: - links: - GetUserByUserId: - operationId: getUser - parameters: - userId: '$response.body#/id' - description: The id value returned in the response can be used as the userId parameter in GET /users/{userId}"; + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +"; + // OpenApi doc with local $ref private const string OpenApi_2 = @" openapi: 3.0.0 info: version: 0.0.0 title: Links example +servers: + - url: https://myserver.com/beta paths: /users: post: @@ -90,6 +99,13 @@ public class OpenApiLinkReferenceTests links: GetUserByUserId: $ref: '#/components/links/GetUserByUserId' # <---- referencing the link here +components: + links: + GetUserByUserId: + operationId: getUser + parameters: + userId: '$response.body#/id' + description: The id value returned in the response can be used as the userId parameter in GET /users/{userId} "; private readonly OpenApiLinkReference _localLinkReference; @@ -102,15 +118,14 @@ public OpenApiLinkReferenceTests() var reader = new OpenApiStringReader(); _openApiDoc = reader.Read(OpenApi, out _); _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/linkreferencesample", _openApiDoc); + _openApiDoc.Workspace.AddDocument( _openApiDoc_2); - _localLinkReference = new("GetUserByUserId", _openApiDoc) + _localLinkReference = new("GetUserByUserId", _openApiDoc_2) { Description = "Use the id returned as the userId in `GET /users/{userId}`" }; - _externalLinkReference = new("GetUserByUserId", _openApiDoc_2, "http://localhost/linkreferencesample") + _externalLinkReference = new("GetUserByUserId", _openApiDoc, "https://myserver.com/beta") { Description = "Externally referenced: Use the id returned as the userId in `GET /users/{userId}`" }; @@ -120,12 +135,17 @@ public OpenApiLinkReferenceTests() public void LinkReferenceResolutionWorks() { // Assert - Assert.Equal("Use the id returned as the userId in `GET /users/{userId}`", _localLinkReference.Description); Assert.Equal("getUser", _localLinkReference.OperationId); Assert.Equal("userId", _localLinkReference.Parameters.First().Key); + Assert.Equal("Use the id returned as the userId in `GET /users/{userId}`", _localLinkReference.Description); + + Assert.Equal("getUser", _externalLinkReference.OperationId); + Assert.Equal("userId", _localLinkReference.Parameters.First().Key); Assert.Equal("Externally referenced: Use the id returned as the userId in `GET /users/{userId}`", _externalLinkReference.Description); + + // The main description and summary values shouldn't change Assert.Equal("The id value returned in the response can be used as the userId parameter in GET /users/{userId}", - _openApiDoc.Components.Links.First().Value.Description); // The main description value shouldn't change + _openApiDoc_2.Components.Links.FirstOrDefault().Value.Description); // The main description value shouldn't change } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs index 593c76761..8cec017aa 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs @@ -18,37 +18,42 @@ namespace Microsoft.OpenApi.Tests.Models.References [UsesVerify] public class OpenApiParameterReferenceTests { + // OpenApi doc with external $ref private const string OpenApi = @" openapi: 3.0.0 info: title: Sample API version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 paths: /users: get: summary: Get users parameters: - - $ref: '#/components/parameters/limitParam' + - $ref: 'https://myserver.com/beta#/components/parameters/limitParam' responses: 200: description: Successful operation components: - parameters: - limitParam: - name: limit - in: query - description: Number of results to return - schema: - type: integer - minimum: 1 - maximum: 100 + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string "; + // OpenApi doc with local $ref private const string OpenApi_2 = @" openapi: 3.0.0 info: title: Sample API version: 1.0.0 +servers: + - url: https://myserver.com/beta paths: /users: get: @@ -58,6 +63,16 @@ public class OpenApiParameterReferenceTests responses: 200: description: Successful operation +components: + parameters: + limitParam: + name: limit + in: query + description: Number of results to return + schema: + type: integer + minimum: 1 + maximum: 100 "; private readonly OpenApiParameterReference _localParameterReference; private readonly OpenApiParameterReference _externalParameterReference; @@ -69,15 +84,14 @@ public OpenApiParameterReferenceTests() var reader = new OpenApiStringReader(); _openApiDoc = reader.Read(OpenApi, out _); _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/parameterreference", _openApiDoc); + _openApiDoc.Workspace.AddDocument(_openApiDoc_2); - _localParameterReference = new("limitParam", _openApiDoc) + _localParameterReference = new("limitParam", _openApiDoc_2) { Description = "Results to return" }; - _externalParameterReference = new OpenApiParameterReference("limitParam", _openApiDoc_2, "http://localhost/parameterreference") + _externalParameterReference = new OpenApiParameterReference("limitParam", _openApiDoc, "https://myserver.com/beta") { Description = "Externally referenced: Results to return" }; @@ -89,9 +103,10 @@ public void ParameterReferenceResolutionWorks() // Assert Assert.Equal("limit", _localParameterReference.Name); Assert.Equal("Results to return", _localParameterReference.Description); + Assert.Equal("limit", _externalParameterReference.Name); Assert.Equal("Externally referenced: Results to return", _externalParameterReference.Description); Assert.Equal("Number of results to return", - _openApiDoc.Components.Parameters.First().Value.Description); // The main description value shouldn't change + _openApiDoc_2.Components.Parameters.First().Value.Description); // The main description value shouldn't change } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs index 86a82aacc..a49221fba 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs @@ -23,10 +23,32 @@ public class OpenApiPathItemReferenceTests info: title: Sample API version: 1.0.0 +servers: + - url: https://myserver.com/v1.0 paths: /users: - $ref: '#/components/pathItems/userPathItem' + $ref: 'https://myserver.com/beta#/components/pathItems/userPathItem' +components: + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string +"; + private const string OpenApi_2 = @" +openapi: 3.0.0 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://myserver.com/beta +paths: + /users: + $ref: '#/components/pathItems/userPathItem' components: pathItems: userPathItem: @@ -49,16 +71,6 @@ public class OpenApiPathItemReferenceTests description: User deleted successfully "; - private const string OpenApi_2 = @" -openapi: 3.0.0 -info: - title: Sample API - version: 1.0.0 -paths: - /users: - $ref: '#/components/pathItems/userPathItem' -"; - private readonly OpenApiPathItemReference _localPathItemReference; private readonly OpenApiPathItemReference _externalPathItemReference; private readonly OpenApiDocument _openApiDoc; @@ -69,16 +81,15 @@ public OpenApiPathItemReferenceTests() var reader = new OpenApiStringReader(); _openApiDoc = reader.Read(OpenApi, out _); _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/pathitemreference", _openApiDoc); + _openApiDoc.Workspace.AddDocument(_openApiDoc_2); - _localPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc) + _localPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc_2) { Description = "Local reference: User path item description", Summary = "Local reference: User path item summary" }; - _externalPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc_2, "http://localhost/pathitemreference") + _externalPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc, "https://myserver.com/beta") { Description = "External reference: User path item description", Summary = "External reference: User path item summary" @@ -89,18 +100,20 @@ public OpenApiPathItemReferenceTests() public void PathItemReferenceResolutionWorks() { // Assert + Assert.Equal([OperationType.Get, OperationType.Post, OperationType.Delete], + _localPathItemReference.Operations.Select(o => o.Key)); Assert.Equal(3, _localPathItemReference.Operations.Count); Assert.Equal("Local reference: User path item description", _localPathItemReference.Description); Assert.Equal("Local reference: User path item summary", _localPathItemReference.Summary); - Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Post, OperationType.Delete }, - _localPathItemReference.Operations.Select(o => o.Key)); + Assert.Equal([OperationType.Get, OperationType.Post, OperationType.Delete], + _externalPathItemReference.Operations.Select(o => o.Key)); Assert.Equal("External reference: User path item description", _externalPathItemReference.Description); Assert.Equal("External reference: User path item summary", _externalPathItemReference.Summary); // The main description and summary values shouldn't change - Assert.Equal("User path item description", _openApiDoc.Components.PathItems.First().Value.Description); - Assert.Equal("User path item summary", _openApiDoc.Components.PathItems.First().Value.Summary); + Assert.Equal("User path item description", _openApiDoc_2.Components.PathItems.First().Value.Description); + Assert.Equal("User path item summary", _openApiDoc_2.Components.PathItems.First().Value.Summary); } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs index edfb81e09..48f731457 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs @@ -25,34 +25,26 @@ public class OpenApiRequestBodyReferenceTests info: title: Sample API version: 1.0.0 - +servers: + - url: https://myserver.com/v1.0 paths: /users: post: summary: Create a user requestBody: - $ref: '#/components/requestBodies/UserRequest' # <---- referencing the requestBody here + $ref: 'https://myserver.com/beta#/components/requestBodies/UserRequest' # <---- externally referencing the requestBody here responses: '201': description: User created - components: - requestBodies: - UserRequest: - description: User creation request body - content: - application/json: - schema: - $ref: '#/components/schemas/UserSchema' - schemas: - UserSchema: + User: type: object properties: + id: + type: integer name: - type: string - email: - type: string + type: string "; private readonly string OpenApi_2 = @" @@ -60,7 +52,8 @@ public class OpenApiRequestBodyReferenceTests info: title: Sample API version: 1.0.0 - +servers: + - url: https://myserver.com/beta paths: /users: post: @@ -70,6 +63,22 @@ public class OpenApiRequestBodyReferenceTests responses: '201': description: User created +components: + requestBodies: + UserRequest: + description: User creation request body + content: + application/json: + schema: + $ref: '#/components/schemas/UserSchema' + schemas: + UserSchema: + type: object + properties: + name: + type: string + email: + type: string "; private readonly OpenApiRequestBodyReference _localRequestBodyReference; @@ -82,15 +91,14 @@ public OpenApiRequestBodyReferenceTests() var reader = new OpenApiStringReader(); _openApiDoc = reader.Read(OpenApi, out _); _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/requestbodyreference", _openApiDoc); + _openApiDoc.Workspace.AddDocument(_openApiDoc_2); - _localRequestBodyReference = new("UserRequest", _openApiDoc) + _localRequestBodyReference = new("UserRequest", _openApiDoc_2) { Description = "User request body" }; - _externalRequestBodyReference = new("UserRequest", _openApiDoc_2, "http://localhost/requestbodyreference") + _externalRequestBodyReference = new("UserRequest", _openApiDoc, "https://myserver.com/beta") { Description = "External Reference: User request body" }; @@ -100,20 +108,18 @@ public OpenApiRequestBodyReferenceTests() public void RequestBodyReferenceResolutionWorks() { // Assert - var expectedSchema = new JsonSchemaBuilder() - .Ref("#/components/schemas/UserSchema") - .Type(SchemaValueType.Object) - .Properties( - ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), - ("email", new JsonSchemaBuilder().Type(SchemaValueType.String))) - .Build(); - var actualSchema = _localRequestBodyReference.Content["application/json"].Schema; - - actualSchema.Should().BeEquivalentTo(expectedSchema); + var localContent = _localRequestBodyReference.Content.Values.FirstOrDefault(); + Assert.NotNull(localContent); + Assert.Equal("#/components/schemas/UserSchema", localContent.Schema.GetRef().OriginalString); Assert.Equal("User request body", _localRequestBodyReference.Description); Assert.Equal("application/json", _localRequestBodyReference.Content.First().Key); + + var externalContent = _externalRequestBodyReference.Content.Values.FirstOrDefault(); + Assert.NotNull(externalContent); + Assert.Equal("#/components/schemas/UserSchema", externalContent.Schema.GetRef().OriginalString); + Assert.Equal("External Reference: User request body", _externalRequestBodyReference.Description); - Assert.Equal("User creation request body", _openApiDoc.Components.RequestBodies.First().Value.Description); + Assert.Equal("User creation request body", _openApiDoc_2.Components.RequestBodies.First().Value.Description); } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs index 681d29e83..46c67b7b4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs @@ -1,11 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models.References; @@ -25,22 +24,14 @@ public class OpenApiResponseReferenceTest info: title: Sample API version: 1.0.0 - +servers: + - url: https://myserver.com/v1.0 paths: /ping: get: responses: '200': - $ref: '#/components/responses/OkResponse' - -components: - responses: - OkResponse: - description: OK - content: - text/plain: - schema: - $ref: '#/components/schemas/Pong' + $ref: 'https://myserver.com/beta#/components/responses/OkResponse' "; private const string OpenApi_2 = @" @@ -48,13 +39,22 @@ public class OpenApiResponseReferenceTest info: title: Sample API version: 1.0.0 - +servers: + - url: https://myserver.com/beta paths: /ping: get: responses: '200': $ref: '#/components/responses/OkResponse' +components: + responses: + OkResponse: + description: OK + content: + text/plain: + schema: + $ref: '#/components/schemas/Pong' "; private readonly OpenApiResponseReference _localResponseReference; @@ -67,15 +67,14 @@ public OpenApiResponseReferenceTest() var reader = new OpenApiStringReader(); _openApiDoc = reader.Read(OpenApi, out _); _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/responsereference", _openApiDoc); + _openApiDoc.Workspace.AddDocument(_openApiDoc_2); - _localResponseReference = new("OkResponse", _openApiDoc) + _localResponseReference = new("OkResponse", _openApiDoc_2) { Description = "OK response" }; - _externalResponseReference = new("OkResponse", _openApiDoc_2, "http://localhost/responsereference") + _externalResponseReference = new("OkResponse", _openApiDoc, "https://myserver.com/beta") { Description = "External reference: OK response" }; @@ -85,11 +84,17 @@ public OpenApiResponseReferenceTest() public void ResponseReferenceResolutionWorks() { // Assert + var localContent = _localResponseReference.Content.FirstOrDefault(); + Assert.Equal("text/plain", localContent.Key); + Assert.Equal("#/components/schemas/Pong", localContent.Value.Schema.GetRef().OriginalString); Assert.Equal("OK response", _localResponseReference.Description); - Assert.Equal("text/plain", _localResponseReference.Content.First().Key); - Assert.NotNull(_localResponseReference.Content.First().Value.Schema.GetRef()); + + var externalContent = _externalResponseReference.Content.FirstOrDefault(); + Assert.Equal("text/plain", externalContent.Key); + Assert.Equal("#/components/schemas/Pong", externalContent.Value.Schema.GetRef().OriginalString); Assert.Equal("External reference: OK response", _externalResponseReference.Description); - Assert.Equal("OK", _openApiDoc.Components.Responses.First().Value.Description); + + Assert.Equal("OK", _openApiDoc_2.Components.Responses.First().Value.Description); } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs index a0bf9ea38..60366ccd5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs @@ -22,7 +22,8 @@ public class OpenApiSecuritySchemeReferenceTests info: title: Sample API version: 1.0.0 - +servers: + - url: https://myserver.com/v1.0 paths: /users: get: From 76512a24360e7d727b338aaa96dac7581262d1ee Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 25 Mar 2024 20:43:54 +0300 Subject: [PATCH 05/34] Register components in document deserializers --- .../V2/OpenApiDocumentDeserializer.cs | 7 ++++++- .../V3/OpenApiComponentsDeserializer.cs | 7 ------- .../V3/OpenApiDocumentDeserializer.cs | 5 +++++ .../V31/OpenApiComponentsDeserializer.cs | 6 ------ .../V31/OpenApiDocumentDeserializer.cs | 7 ++++++- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs index 97c194098..1da780210 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs @@ -263,7 +263,12 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) MakeServers(openApidoc.Servers, openApiNode.Context, rootNode); FixRequestBodyReferences(openApidoc); - RegisterComponentsSchemasInGlobalRegistry(openApidoc.Components?.Schemas); + + // Register components + if (openApidoc.Components != null) + { + openApidoc.Workspace.RegisterComponents(openApidoc); + } return openApidoc; } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs index 53790ac5f..d99f489d5 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs @@ -44,13 +44,6 @@ public static OpenApiComponents LoadComponents(ParseNode node) var components = new OpenApiComponents(); ParseMap(mapNode, components, _componentsFixedFields, _componentsPatternFields); - - foreach (var schema in components.Schemas) - { - var refUri = new Uri(OpenApiConstants.V3ReferenceUri + schema.Key); - SchemaRegistry.Global.Register(refUri, schema.Value); - } - return components; } } diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs index 195576bc1..f25687530 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs @@ -51,6 +51,11 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); + if (openApidoc.Components != null) + { + openApidoc.Workspace.RegisterComponents(openApidoc); + } + return openApidoc; } } diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiComponentsDeserializer.cs index d5532af41..ea15ee6bc 100644 --- a/src/Microsoft.OpenApi.Readers/V31/OpenApiComponentsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiComponentsDeserializer.cs @@ -42,12 +42,6 @@ public static OpenApiComponents LoadComponents(ParseNode node) ParseMap(mapNode, components, _componentsFixedFields, _componentsPatternFields); - foreach (var schema in components.Schemas) - { - var refUri = new Uri(OpenApiConstants.V3ReferenceUri + schema.Key); - SchemaRegistry.Global.Register(refUri, schema.Value); - } - return components; } } diff --git a/src/Microsoft.OpenApi.Readers/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V31/OpenApiDocumentDeserializer.cs index f788755cb..44fc528bc 100644 --- a/src/Microsoft.OpenApi.Readers/V31/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V31/OpenApiDocumentDeserializer.cs @@ -9,7 +9,7 @@ namespace Microsoft.OpenApi.Readers.V31 /// runtime Open API object model. /// internal static partial class OpenApiV31Deserializer - { + { private static readonly FixedFieldMap _openApiFixedFields = new() { { @@ -50,6 +50,11 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); + if (openApidoc.Components != null) + { + openApidoc.Workspace.RegisterComponents(openApidoc); + } + return openApidoc; } } From 4bd1685f98dac62c29a9c5c5e7a98593237da4e5 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Mon, 25 Mar 2024 20:45:17 +0300 Subject: [PATCH 06/34] Add document to its workspace when created via parameterless constructor --- src/Microsoft.OpenApi/Models/OpenApiDocument.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 6563372ff..59694a761 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; @@ -23,7 +23,7 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo /// /// Related workspace containing OpenApiDocuments that are referenced in this document /// - public OpenApiWorkspace Workspace { get; set; } = new(); + public OpenApiWorkspace Workspace { get; set; } /// /// REQUIRED. Provides metadata about the API. The metadata MAY be used by tooling as required. @@ -88,20 +88,14 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo /// public Uri BaseUri { get; } - /// - /// - /// - public string DocumentID { get; } - /// /// Parameter-less constructor /// public OpenApiDocument() { - var documentId = (Servers.FirstOrDefault()?.Url.ToString()) - ?? "http://openapi.net/" + HashCode; - DocumentID = documentId; - Workspace.AddDocument(documentId, this); + BaseUri = new Uri ("http://openapi.net/" + Guid.NewGuid()); + Workspace = new OpenApiWorkspace(BaseUri); + Workspace.AddDocument(this); } /// From 5cea77e407f41a1b9271fa105896351962ffb276 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 27 Mar 2024 15:25:26 +0300 Subject: [PATCH 07/34] Use document Workspace instance --- .../V2/OpenApiDocumentDeserializer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs index 1da780210..96f4a9213 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs @@ -265,10 +265,10 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) FixRequestBodyReferences(openApidoc); // Register components - if (openApidoc.Components != null) - { - openApidoc.Workspace.RegisterComponents(openApidoc); - } + //if (openApidoc.Components != null) + //{ + // openApidoc.Workspace.RegisterComponents(openApidoc.BaseUri, openApidoc.Components); + //} return openApidoc; } From 351baec2031e472bf1d18f28f945fc24b3bc2480 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 27 Mar 2024 15:27:18 +0300 Subject: [PATCH 08/34] Use the document Workspace instance already created in the ctor --- src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs index eb8896f66..c41133fbb 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs @@ -142,11 +142,11 @@ public async Task ReadAsync(JsonNode input, CancellationToken cancel private Task LoadExternalRefs(OpenApiDocument document, CancellationToken cancellationToken = default) { // Create workspace for all documents to live in. - var openApiWorkSpace = new OpenApiWorkspace(); + // var openApiWorkSpace = new OpenApiWorkspace(); // Load this root document into the workspace var streamLoader = new DefaultStreamLoader(_settings.BaseUrl); - var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings); + var workspaceLoader = new OpenApiWorkspaceLoader(document.Workspace, _settings.CustomExternalLoader ?? streamLoader, _settings); return workspaceLoader.LoadAsync(new() { ExternalResource = "/" }, document, null, cancellationToken); } From a5af833ba0fa0484ad6755f81a25d036d9d84463 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 27 Mar 2024 15:28:58 +0300 Subject: [PATCH 09/34] Don't register components directly --- .../V3/OpenApiDocumentDeserializer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs index f25687530..7b41d533c 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs @@ -51,11 +51,11 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); - if (openApidoc.Components != null) - { - openApidoc.Workspace.RegisterComponents(openApidoc); - } - + //if (openApidoc.Components != null) + //{ + // openApidoc.Workspace.RegisterComponents(openApidoc); + //} + return openApidoc; } } From 8acb0d28c4d729112ee984c7c522d13fd24f1774 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 27 Mar 2024 15:29:36 +0300 Subject: [PATCH 10/34] Remove ref resolution from doc.; update ctor --- .../Models/OpenApiDocument.cs | 70 +++---------------- 1 file changed, 10 insertions(+), 60 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 59694a761..9bfbd64a6 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -1,10 +1,11 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using Json.Schema; @@ -93,9 +94,10 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo /// public OpenApiDocument() { - BaseUri = new Uri ("http://openapi.net/" + Guid.NewGuid()); - Workspace = new OpenApiWorkspace(BaseUri); - Workspace.AddDocument(this); + // BaseUri = new Uri("http://openapi.net/document/" + Guid.NewGuid()); + //_docId = Guid.NewGuid().ToString(); + Workspace = new OpenApiWorkspace(); + Workspace.AddDocument("/", this); } /// @@ -554,65 +556,13 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool return null; } - if (this.Components == null) - { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); - } + IOpenApiReferenceable resolvedReference = Workspace.ResolveReference(reference.Id, reference.Type, Components); - try + if (resolvedReference != null) { - switch (reference.Type) - { - case ReferenceType.PathItem: - var resolvedPathItem = this.Components.PathItems[reference.Id]; - resolvedPathItem.Description = reference.Description ?? resolvedPathItem.Description; - resolvedPathItem.Summary = reference.Summary ?? resolvedPathItem.Summary; - return resolvedPathItem; - - case ReferenceType.Response: - var resolvedResponse = this.Components.Responses[reference.Id]; - resolvedResponse.Description = reference.Description ?? resolvedResponse.Description; - return resolvedResponse; - - case ReferenceType.Parameter: - var resolvedParameter = this.Components.Parameters[reference.Id]; - resolvedParameter.Description = reference.Description ?? resolvedParameter.Description; - return resolvedParameter; - - case ReferenceType.Example: - var resolvedExample = this.Components.Examples[reference.Id]; - resolvedExample.Summary = reference.Summary ?? resolvedExample.Summary; - resolvedExample.Description = reference.Description ?? resolvedExample.Description; - return resolvedExample; - - case ReferenceType.RequestBody: - var resolvedRequestBody = this.Components.RequestBodies[reference.Id]; - resolvedRequestBody.Description = reference.Description ?? resolvedRequestBody.Description; - return resolvedRequestBody; - - case ReferenceType.Header: - var resolvedHeader = this.Components.Headers[reference.Id]; - resolvedHeader.Description = reference.Description ?? resolvedHeader.Description; - return resolvedHeader; - - case ReferenceType.SecurityScheme: - var resolvedSecurityScheme = this.Components.SecuritySchemes[reference.Id]; - resolvedSecurityScheme.Description = reference.Description ?? resolvedSecurityScheme.Description; - return resolvedSecurityScheme; - - case ReferenceType.Link: - var resolvedLink = this.Components.Links[reference.Id]; - resolvedLink.Description = reference.Description ?? resolvedLink.Description; - return resolvedLink; - - case ReferenceType.Callback: - return this.Components.Callbacks[reference.Id]; - - default: - throw new OpenApiException(Properties.SRResource.InvalidReferenceType); - } + return resolvedReference; } - catch (KeyNotFoundException) + else { throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); } From 2cfaf7a127d914b467cef800e2bdff3879e28639 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 27 Mar 2024 15:32:26 +0300 Subject: [PATCH 11/34] Update workspace; add new methods --- .../Services/OpenApiWorkspace.cs | 320 +++++++++++++----- 1 file changed, 233 insertions(+), 87 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index 72c5cb030..0edd0acd6 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using Json.Schema; +using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -21,9 +22,6 @@ public class OpenApiWorkspace private readonly Dictionary _fragments = new(); private readonly Dictionary _schemaFragments = new(); private readonly Dictionary _artifacts = new(); - private IDictionary _referenceableRegistry = new Dictionary(); - private IDictionary _schemaRegistry = new Dictionary(); - /// /// A list of OpenApiDocuments contained in the workspace @@ -65,7 +63,7 @@ public OpenApiWorkspace(Uri baseUrl) /// public OpenApiWorkspace() { - BaseUrl = new("file://" + Environment.CurrentDirectory + $"{Path.DirectorySeparatorChar}" ); + BaseUrl = new("http://openapi.net/workspace/"); } /// @@ -73,84 +71,54 @@ public OpenApiWorkspace() /// public OpenApiWorkspace(OpenApiWorkspace workspace) { } + /// + /// + /// + public IDictionary ComponentsRegistry { get; } = new Dictionary(); + /// /// /// /// - /// - public void RegisterComponent(Uri uri, IBaseDocument baseDocument) + /// + /// + public void RegisterComponents(Uri uri, OpenApiComponents components) { if (uri == null) throw new ArgumentNullException(nameof(uri)); - if (baseDocument == null) throw new ArgumentNullException(nameof(baseDocument)); - - if (_schemaRegistry.ContainsKey(uri.ToString())) - { - throw new InvalidOperationException($"Key already exists. {nameof(uri)} needs to be unique"); - } - else - { - _schemaRegistry.Add(uri.OriginalString, baseDocument); - } + if (components == null) throw new ArgumentNullException(nameof(components)); + ComponentsRegistry[uri] = components; } /// /// /// - /// - /// - public void RegisterComponent(Uri uri, IOpenApiReferenceable referenceable) + /// + /// + public void RegisterComponents(OpenApiDocument document) { - if (uri == null) throw new ArgumentNullException(nameof(uri)); - if (referenceable == null) throw new ArgumentNullException(nameof(referenceable)); - - if (_referenceableRegistry.ContainsKey(uri.OriginalString)) - { - throw new InvalidOperationException($"Key already exists. {nameof(uri)} needs to be unique"); - } - else - { - _referenceableRegistry.Add(uri.OriginalString, referenceable); - } + if (document == null) throw new ArgumentNullException(nameof(document)); + if (document.Components == null) throw new ArgumentNullException(nameof(document.Components)); + ComponentsRegistry[GetDocumentUri(document)] = document.Components; } /// /// /// - /// /// - /// + /// /// - public bool TryRetrieveComponent(Uri uri, out TValue value) + public bool TryGetComponents(Uri uri, out OpenApiComponents components) { if (uri == null) { - value = default; + components = null; return false; } - - if ((typeof(TValue) == typeof(IBaseDocument))) - { - _schemaRegistry.TryGetValue(uri.OriginalString, out IBaseDocument schema); - if (schema != null) - { - value = (TValue)schema; - return true; - } - } - else if(typeof(TValue) == typeof(IOpenApiReferenceable)) - { - _referenceableRegistry.TryGetValue(uri.OriginalString, out IOpenApiReferenceable referenceable); - if (referenceable != null) - { - value = (TValue)referenceable; - return true; - } - } - value = default; - return false; + ComponentsRegistry.TryGetValue(uri, out components); + return (components != null); } - + /// /// Verify if workspace contains a document based on its URL. /// @@ -165,12 +133,50 @@ public bool Contains(string location) /// /// Add an OpenApiDocument to the workspace. /// - /// - /// + /// The string location. + /// The OpenAPI document. public void AddDocument(string location, OpenApiDocument document) { document.Workspace = this; - _documents.Add(ToLocationUrl(location), document); + var locationUrl = ToLocationUrl(location); + _documents.Add(locationUrl, document); + if (document.Components != null) + { + RegisterComponents(locationUrl, document.Components); + } + } + + /// + /// Add an OpenApiDocument to the workspace. + /// + /// The OpenAPI document. + public void AddDocument(OpenApiDocument document) + { + // document.Workspace = this; TODO + + // Register components in this doc. + if (document.Components != null) + { + RegisterComponents(GetDocumentUri(document), document.Components); + } + } + + /// + /// + /// + /// + /// + private Uri GetDocumentUri(OpenApiDocument document) + { + if (document == null) return null; + + string docUri = (document.Servers.FirstOrDefault() != null) ? document.Servers.First().Url : document.BaseUri.OriginalString; + if (!Uri.TryCreate(docUri, UriKind.Absolute, out _)) + { + docUri = $"http://openapi.net/{docUri}"; + } + + return new Uri(docUri); } /// @@ -193,7 +199,11 @@ public void AddFragment(string location, IOpenApiReferenceable fragment) /// public void AddSchemaFragment(string location, JsonSchema fragment) { - _schemaFragments.Add(ToLocationUrl(location), fragment); + var locationUri = ToLocationUrl(location); + _schemaFragments.Add(locationUri, fragment); + var schemaComponent = new OpenApiComponents(); + schemaComponent.Schemas.Add(locationUri.OriginalString, fragment); + ComponentsRegistry[locationUri] = schemaComponent; } /// @@ -213,53 +223,176 @@ public void AddArtifact(string location, Stream artifact) /// public IOpenApiReferenceable ResolveReference(OpenApiReference reference) { - if (_documents.TryGetValue(new(BaseUrl, reference.ExternalResource), out var doc)) + var uri = new Uri(BaseUrl, reference.ExternalResource); + if (_documents.TryGetValue(uri, out var doc)) { - return doc.ResolveReference(reference, false); + // return doc.ResolveReference(reference, false); // TODO: Resolve internally, don't refer to doc. + return ResolveReference(reference.Id, reference.Type, doc.Components); } - else if (_fragments.TryGetValue(new(BaseUrl, reference.ExternalResource), out var fragment)) + else if (_fragments.TryGetValue(uri, out var fragment)) { var jsonPointer = new JsonPointer($"/{reference.Id ?? string.Empty}"); return fragment.ResolveReference(jsonPointer); } return null; + } + + //public JsonSchema ResolveJsonSchemaReference(Uri reference) + // { + // TryResolveReference(reference.OriginalString, ReferenceType.Schema, document.BaseUri, out var resolvedSchema); + + // if (resolvedSchema != null) + // { + // var resolvedSchemaBuilder = new JsonSchemaBuilder(); + // var description = resolvedSchema.GetDescription(); + // var summary = resolvedSchema.GetSummary(); + + // foreach (var keyword in resolvedSchema.Keywords) + // { + // resolvedSchemaBuilder.Add(keyword); + + // // Replace the resolved schema's description with that of the schema reference + // if (!string.IsNullOrEmpty(description)) + // { + // resolvedSchemaBuilder.Description(description); + // } + + // // Replace the resolved schema's summary with that of the schema reference + // if (!string.IsNullOrEmpty(summary)) + // { + // resolvedSchemaBuilder.Summary(summary); + // } + // } + + // return resolvedSchemaBuilder.Build(); + // } + // else + // { + // var referenceId = reference.OriginalString.Split('/').LastOrDefault(); + // throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); + // } + //} + /// - /// Resolve the target of a JSON schema reference from within the workspace + /// /// - /// An instance of a JSON schema reference. + /// + /// + /// + /// + /// /// - public JsonSchema ResolveJsonSchemaReference(Uri reference) + /// + public bool TryResolveReference(string referenceV3, ReferenceType? referenceType, out T value, Uri docBaseUri = null) { - var docs = _documents.Values; - if (docs.Any()) + value = default; + if (string.IsNullOrEmpty(referenceV3)) return false; + + var referenceId = referenceV3.Split('/').LastOrDefault(); + + // The first part of the referenceId before the # should give us our location url + // if the 1st part is missing, then the reference is in the entry document + var locationUrl = (referenceV3.Contains('#')) ? referenceV3.Substring(0, referenceV3.IndexOf('#')) : null; + + ComponentsRegistry.TryGetValue(docBaseUri, out var componentsTest); + + OpenApiComponents components; + if (string.IsNullOrEmpty(locationUrl)) { - var doc = docs.FirstOrDefault(); - if (doc != null) - { - foreach (var jsonSchema in doc.Components.Schemas) - { - var refUri = new Uri(OpenApiConstants.V3ReferenceUri + jsonSchema.Key); - SchemaRegistry.Global.Register(refUri, jsonSchema.Value); - } - - var resolver = new OpenApiReferenceResolver(doc); - return resolver.ResolveJsonSchemaReference(reference); - } - return null; + // Get the entry level document components + // or the 1st registry component (if entry level has no components) + components = ComponentsRegistry.FirstOrDefault().Value; } else { - foreach (var jsonSchema in _schemaFragments) - { - SchemaRegistry.Global.Register(reference, jsonSchema.Value); - } + // Try convert to absolute uri + Uri uriLocation = ToLocationUrl(locationUrl); + + ComponentsRegistry.TryGetValue(uriLocation, out components); + } + + if (components == null) return false; + + switch (referenceType) + { + case ReferenceType.PathItem: + value = (T)(IOpenApiReferenceable)components.PathItems[referenceId]; + return (value != null); + + case ReferenceType.Response: + value = (T)(IOpenApiReferenceable)components.Responses[referenceId]; + return (value != null); + + case ReferenceType.Parameter: + value = (T)(IOpenApiReferenceable)components.Parameters[referenceId]; + return (value != null); + + case ReferenceType.Example: + value = (T)(IOpenApiReferenceable)components.Examples[referenceId]; + return (value != null); + + case ReferenceType.RequestBody: + value = (T)(IOpenApiReferenceable)components.RequestBodies[referenceId]; + return (value != null); - return FetchSchemaFromRegistry(reference); + case ReferenceType.Header: + value = (T)(IOpenApiReferenceable)components.Headers[referenceId]; + return (value != null); + + case ReferenceType.SecurityScheme: + value = (T)(IOpenApiReferenceable)components.SecuritySchemes[referenceId]; + return (value != null); + + case ReferenceType.Link: + value = (T)(IOpenApiReferenceable)components.Links[referenceId]; + return (value != null); + + case ReferenceType.Callback: + value = (T)(IOpenApiReferenceable)components.Callbacks[referenceId]; + return (value != null); + + case ReferenceType.Schema: + value = (T)(IBaseDocument)components.Schemas[referenceId]; + return (value != null); + + default: + throw new OpenApiException(Properties.SRResource.InvalidReferenceType); } } + /// + /// + /// + /// + /// + /// + /// + /// + /// + public T ResolveReference(string referenceId, ReferenceType? referenceType, OpenApiComponents components) + { + if (string.IsNullOrEmpty(referenceId)) return default; + if (components == null) return default; + + return referenceType switch + { + ReferenceType.PathItem => (T)(IOpenApiReferenceable)components.PathItems[referenceId], + ReferenceType.Response => (T)(IOpenApiReferenceable)components.Responses[referenceId], + ReferenceType.Parameter => (T)(IOpenApiReferenceable)components.Parameters[referenceId], + ReferenceType.Example => (T)(IOpenApiReferenceable)components.Examples[referenceId], + ReferenceType.RequestBody => (T)(IOpenApiReferenceable)components.RequestBodies[referenceId], + ReferenceType.Header => (T)(IOpenApiReferenceable)components.Headers[referenceId], + ReferenceType.SecurityScheme => (T)(IOpenApiReferenceable)components.SecuritySchemes[referenceId], + ReferenceType.Link => (T)(IOpenApiReferenceable)components.Links[referenceId], + ReferenceType.Callback => (T)(IOpenApiReferenceable)components.Callbacks[referenceId], + ReferenceType.Schema => (T)(IBaseDocument)components.Schemas[referenceId], + _ => throw new OpenApiException(Properties.SRResource.InvalidReferenceType), + }; + } + + /// /// /// @@ -272,7 +405,20 @@ public Stream GetArtifact(string location) private Uri ToLocationUrl(string location) { - return new(BaseUrl, location); + // Try convert to absolute uri + return (Uri.TryCreate(location, UriKind.Absolute, out var uri)) == true ? uri : new Uri(BaseUrl, location); + + //if (Uri.TryCreate(location, UriKind.Absolute, out var uri)) + // { + // locationUri = new Uri(BaseUrl, uri.LocalPath); + // } + //else + //{ + // locationUri = new Uri(BaseUrl, location); + //} + //return locationUri; + + // return new(BaseUrl, location); } private static JsonSchema FetchSchemaFromRegistry(Uri reference) From 9bdbac6b8e3f4c455f0ee5b2e86157f4fdc3c566 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 27 Mar 2024 15:34:30 +0300 Subject: [PATCH 12/34] Update tests --- .../OpenApiWorkspaceStreamTests.cs | 10 ++++----- .../TryLoadReferenceV2Tests.cs | 5 ----- ...sync_produceTerseOutput=False.verified.txt | 2 +- ...Async_produceTerseOutput=True.verified.txt | 2 +- .../Workspaces/OpenApiWorkspaceTests.cs | 22 +++++++++---------- 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index 912dc8a5c..9718abbf4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -53,7 +53,7 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo { LoadExternalRefs = true, CustomExternalLoader = new ResourceLoader(), - BaseUrl = new("fie://c:\\") + BaseUrl = new("file://c:\\"), }); ReadResult result; @@ -71,9 +71,9 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo .Content["application/json"] .Schema; - var x = referencedSchema.GetProperties().TryGetValue("subject", out var schema); - Assert.Equal(SchemaValueType.Object, referencedSchema.GetJsonType()); - Assert.Equal(SchemaValueType.String, schema.GetJsonType()); + //var x = referencedSchema.GetProperties().TryGetValue("subject", out var schema); + //Assert.Equal(SchemaValueType.Object, referencedSchema.GetJsonType()); + //Assert.Equal(SchemaValueType.String, schema.GetJsonType()); var referencedParameter = result.OpenApiDocument .Paths["/todos"] diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index d9d4e0eb3..c62f159f4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -160,11 +160,6 @@ public void LoadResponseAndSchemaReference() { Schema = new JsonSchemaBuilder() .Ref("#/definitions/SampleObject2") - .Description("Sample description") - .Required("name") - .Properties( - ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), - ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))) } }, Reference = new() diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index 8b29b212e..11c2db2c7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -1,4 +1,4 @@ { - "description": "Location of the locally created post", + "description": "Location of the locally referenced post", "type": "string" } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index 243908873..a74954cf5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"description":"Location of the locally created post","type":"string"} \ No newline at end of file +{"description":"Location of the locally referenced post","type":"string"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index 57faaf72f..c9522447b 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -74,13 +74,12 @@ public void OpenApiWorkspacesCanResolveExternalReferences() { var refUri = new Uri("https://everything.json/common#/components/schemas/test"); var workspace = new OpenApiWorkspace(); - var doc = CreateCommonDocument(refUri); - var location = "common"; - - workspace.AddDocument(location, doc); + var externalDoc = CreateCommonDocument(refUri); + + workspace.AddDocument("https://everything.json/common", externalDoc); + + workspace.TryResolveReference("https://everything.json/common#/components/schemas/test", ReferenceType.Schema, out var schema); - var schema = workspace.ResolveJsonSchemaReference(refUri); - Assert.NotNull(schema); Assert.Equal("The referenced one", schema.GetDescription()); } @@ -148,7 +147,7 @@ public void OpenApiWorkspacesCanResolveReferencesToDocumentFragments() workspace.AddSchemaFragment("fragment", schemaFragment); // Act - var schema = workspace.ResolveJsonSchemaReference(new Uri("https://everything.json/common#/components/schemas/test")); + workspace.TryResolveReference("https://everything.json/common#/components/schemas/test", ReferenceType.Schema, out var schema); // Assert Assert.NotNull(schema); @@ -193,10 +192,11 @@ private static OpenApiDocument CreateCommonDocument(Uri refUri) } }; - foreach(var schema in doc.Components.Schemas) - { - SchemaRegistry.Global.Register(refUri, schema.Value); - } + //foreach(var schema in doc.Components.Schemas) + //{ + // SchemaRegistry.Global.Register(refUri, schema.Value); + //} + return doc; } From dd3f13ca6cb4da4199a8e300e72bf8dc8e2b6b8b Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 28 Mar 2024 16:29:54 +0300 Subject: [PATCH 13/34] Add JsonSchema reference resolution to OpenApiDocument class --- .../Models/OpenApiDocument.cs | 39 ++++++++++++------- .../Services/OpenApiReferenceResolver.cs | 6 +-- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 82676e857..2ffd67361 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -97,8 +97,6 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo /// public OpenApiDocument() { - // BaseUri = new Uri("http://openapi.net/document/" + Guid.NewGuid()); - //_docId = Guid.NewGuid().ToString(); Workspace = new OpenApiWorkspace(); Workspace.AddDocument("/", this); } @@ -485,6 +483,29 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) return ResolveReference(reference, false); } + /// + /// Resolves JsonSchema refs + /// + /// + /// A JsonSchema ref. + public JsonSchema ResolveJsonSchemaReference(Uri referenceUri) + { + if (referenceUri == null) return null; + + OpenApiReference reference = new OpenApiReference() + { + ExternalResource = referenceUri.OriginalString, + Id = referenceUri.OriginalString.Split('/').Last(), + Type = ReferenceType.Schema + }; + + JsonSchema resolvedSchema = reference.ExternalResource.StartsWith("#") + ? (JsonSchema)Workspace.ResolveReference(reference.Id, reference.Type, Components) // local ref + : Workspace.ResolveReference(reference); // external ref + + return resolvedSchema ?? throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); + } + /// /// Takes in an OpenApi document instance and generates its hash value /// @@ -536,7 +557,7 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool { throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution); } - return this.Workspace.ResolveReference(reference); + return this.Workspace.ResolveReference(reference); } if (!reference.Type.HasValue) @@ -559,16 +580,8 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool return null; } - IOpenApiReferenceable resolvedReference = Workspace.ResolveReference(reference.Id, reference.Type, Components); - - if (resolvedReference != null) - { - return resolvedReference; - } - else - { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); - } + return Workspace.ResolveReference(reference.Id, reference.Type, Components) + ?? throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); } /// diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs index 43f1b7877..959c9a35a 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs @@ -18,7 +18,6 @@ namespace Microsoft.OpenApi.Services public class OpenApiReferenceResolver : OpenApiVisitorBase { private OpenApiDocument _currentDocument; - private readonly bool _resolveRemoteReferences; private List _errors = new(); /// @@ -251,9 +250,8 @@ private Dictionary ResolveJsonSchemas(IDictionaryThe schema's summary. /// public JsonSchema ResolveJsonSchemaReference(Uri reference, string description = null, string summary = null) - { - var refUri = $"https://registry{reference.OriginalString.Split('#').LastOrDefault()}"; - var resolvedSchema = (JsonSchema)SchemaRegistry.Global.Get(new Uri(refUri)); + { + var resolvedSchema = _currentDocument.ResolveJsonSchemaReference(reference); if (resolvedSchema != null) { From 0e3bf94519e9179059ba4aceb55e45e99ef06ecb Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 28 Mar 2024 16:34:39 +0300 Subject: [PATCH 14/34] Remove unnecessary code; revert BaseUrl value --- .../Services/OpenApiWorkspace.cs | 250 ++---------------- 1 file changed, 20 insertions(+), 230 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index 0edd0acd6..a772eb3cf 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -63,7 +63,7 @@ public OpenApiWorkspace(Uri baseUrl) /// public OpenApiWorkspace() { - BaseUrl = new("http://openapi.net/workspace/"); + BaseUrl = new("file://" + Environment.CurrentDirectory + $"{Path.DirectorySeparatorChar}"); } /// @@ -75,50 +75,7 @@ public OpenApiWorkspace(OpenApiWorkspace workspace) { } /// /// public IDictionary ComponentsRegistry { get; } = new Dictionary(); - - /// - /// - /// - /// - /// - /// - public void RegisterComponents(Uri uri, OpenApiComponents components) - { - if (uri == null) throw new ArgumentNullException(nameof(uri)); - if (components == null) throw new ArgumentNullException(nameof(components)); - ComponentsRegistry[uri] = components; - } - - /// - /// - /// - /// - /// - public void RegisterComponents(OpenApiDocument document) - { - if (document == null) throw new ArgumentNullException(nameof(document)); - if (document.Components == null) throw new ArgumentNullException(nameof(document.Components)); - ComponentsRegistry[GetDocumentUri(document)] = document.Components; - } - - /// - /// - /// - /// - /// - /// - public bool TryGetComponents(Uri uri, out OpenApiComponents components) - { - if (uri == null) - { - components = null; - return false; - } - - ComponentsRegistry.TryGetValue(uri, out components); - return (components != null); - } - + /// /// Verify if workspace contains a document based on its URL. /// @@ -127,7 +84,7 @@ public bool TryGetComponents(Uri uri, out OpenApiComponents components) public bool Contains(string location) { var key = ToLocationUrl(location); - return _documents.ContainsKey(key) || _fragments.ContainsKey(key) || _artifacts.ContainsKey(key); + return _documents.ContainsKey(key) || _fragments.ContainsKey(key) || _artifacts.ContainsKey(key) || _schemaFragments.ContainsKey(key); } /// @@ -139,44 +96,11 @@ public void AddDocument(string location, OpenApiDocument document) { document.Workspace = this; var locationUrl = ToLocationUrl(location); - _documents.Add(locationUrl, document); - if (document.Components != null) - { - RegisterComponents(locationUrl, document.Components); - } - } - - /// - /// Add an OpenApiDocument to the workspace. - /// - /// The OpenAPI document. - public void AddDocument(OpenApiDocument document) - { - // document.Workspace = this; TODO - - // Register components in this doc. - if (document.Components != null) - { - RegisterComponents(GetDocumentUri(document), document.Components); - } - } - /// - /// - /// - /// - /// - private Uri GetDocumentUri(OpenApiDocument document) - { - if (document == null) return null; - - string docUri = (document.Servers.FirstOrDefault() != null) ? document.Servers.First().Url : document.BaseUri.OriginalString; - if (!Uri.TryCreate(docUri, UriKind.Absolute, out _)) + if (!_documents.ContainsKey(locationUrl)) { - docUri = $"http://openapi.net/{docUri}"; + _documents.Add(locationUrl, document); } - - return new Uri(docUri); } /// @@ -200,10 +124,10 @@ public void AddFragment(string location, IOpenApiReferenceable fragment) public void AddSchemaFragment(string location, JsonSchema fragment) { var locationUri = ToLocationUrl(location); - _schemaFragments.Add(locationUri, fragment); - var schemaComponent = new OpenApiComponents(); - schemaComponent.Schemas.Add(locationUri.OriginalString, fragment); - ComponentsRegistry[locationUri] = schemaComponent; + if (!_schemaFragments.ContainsKey(locationUri)) + { + _schemaFragments.Add(locationUri, fragment); + } } /// @@ -217,151 +141,31 @@ public void AddArtifact(string location, Stream artifact) } /// - /// Returns the target of an OpenApiReference from within the workspace. + /// Returns the target of a referenceable item from within the workspace. /// - /// An instance of an OpenApiReference + /// + /// /// - public IOpenApiReferenceable ResolveReference(OpenApiReference reference) + public T ResolveReference(OpenApiReference reference) { var uri = new Uri(BaseUrl, reference.ExternalResource); if (_documents.TryGetValue(uri, out var doc)) { - // return doc.ResolveReference(reference, false); // TODO: Resolve internally, don't refer to doc. - return ResolveReference(reference.Id, reference.Type, doc.Components); + return ResolveReference(reference.Id, reference.Type, doc.Components); } else if (_fragments.TryGetValue(uri, out var fragment)) { var jsonPointer = new JsonPointer($"/{reference.Id ?? string.Empty}"); - return fragment.ResolveReference(jsonPointer); - } - return null; - - } - - - //public JsonSchema ResolveJsonSchemaReference(Uri reference) - // { - // TryResolveReference(reference.OriginalString, ReferenceType.Schema, document.BaseUri, out var resolvedSchema); - - // if (resolvedSchema != null) - // { - // var resolvedSchemaBuilder = new JsonSchemaBuilder(); - // var description = resolvedSchema.GetDescription(); - // var summary = resolvedSchema.GetSummary(); - - // foreach (var keyword in resolvedSchema.Keywords) - // { - // resolvedSchemaBuilder.Add(keyword); - - // // Replace the resolved schema's description with that of the schema reference - // if (!string.IsNullOrEmpty(description)) - // { - // resolvedSchemaBuilder.Description(description); - // } - - // // Replace the resolved schema's summary with that of the schema reference - // if (!string.IsNullOrEmpty(summary)) - // { - // resolvedSchemaBuilder.Summary(summary); - // } - // } - - // return resolvedSchemaBuilder.Build(); - // } - // else - // { - // var referenceId = reference.OriginalString.Split('/').LastOrDefault(); - // throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); - // } - //} - - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public bool TryResolveReference(string referenceV3, ReferenceType? referenceType, out T value, Uri docBaseUri = null) - { - value = default; - if (string.IsNullOrEmpty(referenceV3)) return false; - - var referenceId = referenceV3.Split('/').LastOrDefault(); - - // The first part of the referenceId before the # should give us our location url - // if the 1st part is missing, then the reference is in the entry document - var locationUrl = (referenceV3.Contains('#')) ? referenceV3.Substring(0, referenceV3.IndexOf('#')) : null; - - ComponentsRegistry.TryGetValue(docBaseUri, out var componentsTest); - - OpenApiComponents components; - if (string.IsNullOrEmpty(locationUrl)) - { - // Get the entry level document components - // or the 1st registry component (if entry level has no components) - components = ComponentsRegistry.FirstOrDefault().Value; + return (T)fragment.ResolveReference(jsonPointer); } - else + else if (_schemaFragments.TryGetValue(uri, out var schemaFragment)) { - // Try convert to absolute uri - Uri uriLocation = ToLocationUrl(locationUrl); - - ComponentsRegistry.TryGetValue(uriLocation, out components); + return (T)(schemaFragment as IBaseDocument); } + return default; - if (components == null) return false; - - switch (referenceType) - { - case ReferenceType.PathItem: - value = (T)(IOpenApiReferenceable)components.PathItems[referenceId]; - return (value != null); - - case ReferenceType.Response: - value = (T)(IOpenApiReferenceable)components.Responses[referenceId]; - return (value != null); - - case ReferenceType.Parameter: - value = (T)(IOpenApiReferenceable)components.Parameters[referenceId]; - return (value != null); - - case ReferenceType.Example: - value = (T)(IOpenApiReferenceable)components.Examples[referenceId]; - return (value != null); - - case ReferenceType.RequestBody: - value = (T)(IOpenApiReferenceable)components.RequestBodies[referenceId]; - return (value != null); - - case ReferenceType.Header: - value = (T)(IOpenApiReferenceable)components.Headers[referenceId]; - return (value != null); - - case ReferenceType.SecurityScheme: - value = (T)(IOpenApiReferenceable)components.SecuritySchemes[referenceId]; - return (value != null); - - case ReferenceType.Link: - value = (T)(IOpenApiReferenceable)components.Links[referenceId]; - return (value != null); - - case ReferenceType.Callback: - value = (T)(IOpenApiReferenceable)components.Callbacks[referenceId]; - return (value != null); - - case ReferenceType.Schema: - value = (T)(IBaseDocument)components.Schemas[referenceId]; - return (value != null); - - default: - throw new OpenApiException(Properties.SRResource.InvalidReferenceType); - } } - + /// /// /// @@ -392,7 +196,6 @@ public T ResolveReference(string referenceId, ReferenceType? referenceType, O }; } - /// /// /// @@ -405,20 +208,7 @@ public Stream GetArtifact(string location) private Uri ToLocationUrl(string location) { - // Try convert to absolute uri - return (Uri.TryCreate(location, UriKind.Absolute, out var uri)) == true ? uri : new Uri(BaseUrl, location); - - //if (Uri.TryCreate(location, UriKind.Absolute, out var uri)) - // { - // locationUri = new Uri(BaseUrl, uri.LocalPath); - // } - //else - //{ - // locationUri = new Uri(BaseUrl, location); - //} - //return locationUri; - - // return new(BaseUrl, location); + return new(BaseUrl, location); } private static JsonSchema FetchSchemaFromRegistry(Uri reference) From 276e5eeba6b890306450ab6536086c2f215015e8 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 28 Mar 2024 16:36:06 +0300 Subject: [PATCH 15/34] Update tests --- .../OpenApiWorkspaceStreamTests.cs | 4 +-- .../V2Tests/OpenApiDocumentTests.cs | 20 +++++------ .../OpenApiCallbackReferenceTests.cs | 10 +++--- .../OpenApiExampleReferenceTests.cs | 10 +++--- .../References/OpenApiHeaderReferenceTests.cs | 10 +++--- .../References/OpenApiLinkReferenceTests.cs | 10 +++--- .../OpenApiParameterReferenceTests.cs | 10 +++--- .../OpenApiPathItemReferenceTests.cs | 10 +++--- .../OpenApiRequestBodyReferenceTests.cs | 10 +++--- .../OpenApiResponseReferenceTest.cs | 10 +++--- .../Workspaces/OpenApiWorkspaceTests.cs | 34 ++++++++++--------- 11 files changed, 69 insertions(+), 69 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index b54721f06..ca1455014 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -60,7 +60,7 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo LoadExternalRefs = true, CustomExternalLoader = new ResourceLoader(), BaseUrl = new("file://c:\\"), - }); + }; ReadResult result; result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 754de9e5a..748d441cc 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -162,29 +162,25 @@ public void ShouldAssignSchemaToAllResponses() var successSchema = new JsonSchemaBuilder() .Type(SchemaValueType.Array) .Items(new JsonSchemaBuilder() - .Ref("#/definitions/Item") - .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier.")))) - .Build(); + .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier.")))); var errorSchema = new JsonSchemaBuilder() - .Ref("#/definitions/Error") .Properties(("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), - ("fields", new JsonSchemaBuilder().Type(SchemaValueType.String))) - .Build(); + ("fields", new JsonSchemaBuilder().Type(SchemaValueType.String))); var responses = result.OpenApiDocument.Paths["/items"].Operations[OperationType.Get].Responses; foreach (var response in responses) { - var targetSchema = response.Key == "200" ? successSchema : errorSchema; + var targetSchema = response.Key == "200" ? successSchema.Build() : errorSchema.Build(); var json = response.Value.Content["application/json"]; Assert.NotNull(json); - json.Schema.Should().BeEquivalentTo(targetSchema); + Assert.Equal(json.Schema.Keywords.Count, targetSchema.Keywords.Count); var xml = response.Value.Content["application/xml"]; Assert.NotNull(xml); - xml.Schema.Should().BeEquivalentTo(targetSchema); + Assert.Equal(xml.Schema.Keywords.Count, targetSchema.Keywords.Count); } } @@ -192,8 +188,10 @@ public void ShouldAssignSchemaToAllResponses() public void ShouldAllowComponentsThatJustContainAReference() { // Act - var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "ComponentRootReference.json")); - JsonSchema schema = actual.OpenApiDocument.Components.Schemas["AllPets"]; + var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "ComponentRootReference.json")).OpenApiDocument; + JsonSchema schema = actual.Components.Schemas["AllPets"]; + + schema = actual.ResolveJsonSchemaReference(schema.GetRef()) ?? schema; // Assert if (schema.Keywords.Count.Equals(1) && schema.GetRef() != null) diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs index 1ebbab604..0fbac322a 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.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.Globalization; @@ -133,10 +133,10 @@ public class OpenApiCallbackReferenceTests public OpenApiCallbackReferenceTests() { - var reader = new OpenApiStringReader(); - OpenApiDocument openApiDoc = reader.Read(OpenApi, out _); - OpenApiDocument openApiDoc_2 = reader.Read(OpenApi_2, out _); - openApiDoc.Workspace.AddDocument(openApiDoc_2); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + OpenApiDocument openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + OpenApiDocument openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + openApiDoc.Workspace.AddDocument("https://myserver.com/beta", openApiDoc_2); _externalCallbackReference = new("callbackEvent", openApiDoc, "https://myserver.com/beta"); _localCallbackReference = new("callbackEvent", openApiDoc_2); } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs index fefa52186..56c6c0c1d 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.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.Globalization; @@ -112,10 +112,10 @@ public class OpenApiExampleReferenceTests public OpenApiExampleReferenceTests() { - var reader = new OpenApiStringReader(); - _openApiDoc = reader.Read(OpenApi, out _); - _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc.Workspace.AddDocument(_openApiDoc_2); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); _localExampleReference = new OpenApiExampleReference("UserExample", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs index 80a1178cb..df438a6b9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.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.Globalization; @@ -82,10 +82,10 @@ public class OpenApiHeaderReferenceTests public OpenApiHeaderReferenceTests() { - var reader = new OpenApiStringReader(); - _openApiDoc = reader.Read(OpenApi, out _); - _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc.Workspace.AddDocument( _openApiDoc_2); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); _localHeaderReference = new OpenApiHeaderReference("LocationHeader", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs index b340d6880..6ca010798 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.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.Globalization; @@ -116,10 +116,10 @@ public class OpenApiLinkReferenceTests public OpenApiLinkReferenceTests() { - var reader = new OpenApiStringReader(); - _openApiDoc = reader.Read(OpenApi, out _); - _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc.Workspace.AddDocument( _openApiDoc_2); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); _localLinkReference = new("GetUserByUserId", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs index 3149a82a1..be0e7fa83 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.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.Globalization; @@ -82,10 +82,10 @@ public class OpenApiParameterReferenceTests public OpenApiParameterReferenceTests() { - var reader = new OpenApiStringReader(); - _openApiDoc = reader.Read(OpenApi, out _); - _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc.Workspace.AddDocument(_openApiDoc_2); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); _localParameterReference = new("limitParam", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs index 9c565349b..8bbc05d16 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.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.Globalization; @@ -79,10 +79,10 @@ public class OpenApiPathItemReferenceTests public OpenApiPathItemReferenceTests() { - var reader = new OpenApiStringReader(); - _openApiDoc = reader.Read(OpenApi, out _); - _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc.Workspace.AddDocument(_openApiDoc_2); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); _localPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs index 2d95b44d8..ea2fdb588 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.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.Globalization; @@ -89,10 +89,10 @@ public class OpenApiRequestBodyReferenceTests public OpenApiRequestBodyReferenceTests() { - var reader = new OpenApiStringReader(); - _openApiDoc = reader.Read(OpenApi, out _); - _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc.Workspace.AddDocument(_openApiDoc_2); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); _localRequestBodyReference = new("UserRequest", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs index 4c5e63fe0..cab14f475 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.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.Globalization; @@ -65,10 +65,10 @@ public class OpenApiResponseReferenceTest public OpenApiResponseReferenceTest() { - var reader = new OpenApiStringReader(); - _openApiDoc = reader.Read(OpenApi, out _); - _openApiDoc_2 = reader.Read(OpenApi_2, out _); - _openApiDoc.Workspace.AddDocument(_openApiDoc_2); + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); _localResponseReference = new("OkResponse", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index c9522447b..307132958 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Json.Schema; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; using Xunit; @@ -74,12 +75,12 @@ public void OpenApiWorkspacesCanResolveExternalReferences() { var refUri = new Uri("https://everything.json/common#/components/schemas/test"); var workspace = new OpenApiWorkspace(); - var externalDoc = CreateCommonDocument(refUri); + var externalDoc = CreateCommonDocument(); - workspace.AddDocument("https://everything.json/common", externalDoc); - - workspace.TryResolveReference("https://everything.json/common#/components/schemas/test", ReferenceType.Schema, out var schema); + workspace.AddDocument("common", externalDoc); + var schema = workspace.ResolveReference("test", ReferenceType.Schema, externalDoc.Components); + Assert.NotNull(schema); Assert.Equal("The referenced one", schema.GetDescription()); } @@ -90,7 +91,7 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() var workspace = new OpenApiWorkspace(); var doc = new OpenApiDocument(); - var reference = "#/components/schemas/test"; + var reference = "common#/components/schemas/test"; doc.CreatePathItem("/", p => { p.Description = "Consumer"; @@ -107,7 +108,7 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() var refUri = new Uri("https://registry" + reference.Split('#').LastOrDefault()); workspace.AddDocument("root", doc); - workspace.AddDocument("common", CreateCommonDocument(refUri)); + workspace.AddDocument("common", CreateCommonDocument()); var errors = doc.ResolveReferences(); Assert.Empty(errors); @@ -144,10 +145,17 @@ public void OpenApiWorkspacesCanResolveReferencesToDocumentFragments() // Arrange var workspace = new OpenApiWorkspace(); var schemaFragment = new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Schema from a fragment").Build(); - workspace.AddSchemaFragment("fragment", schemaFragment); + workspace.AddSchemaFragment("common", schemaFragment); // Act - workspace.TryResolveReference("https://everything.json/common#/components/schemas/test", ReferenceType.Schema, out var schema); + var reference = new OpenApiReference() + { + ExternalResource = "common#/components/schemas/test", + Id = "test", + Type = ReferenceType.Schema + }; + + var schema = workspace.ResolveReference(reference); // Assert Assert.NotNull(schema); @@ -169,7 +177,7 @@ public void OpenApiWorkspacesCanResolveReferencesToDocumentFragmentsWithJsonPoin workspace.AddFragment("fragment", responseFragment); // Act - var resolvedElement = workspace.ResolveReference(new() + var resolvedElement = workspace.ResolveReference(new() { Id = "headers/header1", ExternalResource = "fragment" @@ -180,7 +188,7 @@ public void OpenApiWorkspacesCanResolveReferencesToDocumentFragmentsWithJsonPoin } // Test artifacts - private static OpenApiDocument CreateCommonDocument(Uri refUri) + private static OpenApiDocument CreateCommonDocument() { var doc = new OpenApiDocument() { @@ -192,12 +200,6 @@ private static OpenApiDocument CreateCommonDocument(Uri refUri) } }; - //foreach(var schema in doc.Components.Schemas) - //{ - // SchemaRegistry.Global.Register(refUri, schema.Value); - //} - - return doc; } } From 14597de509540b9eab10cd752831b44904985323 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 28 Mar 2024 21:02:04 +0300 Subject: [PATCH 16/34] Remove unused code --- .../Reader/V31/OpenApiDocumentDeserializer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs index b667d2826..37d53dd73 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs @@ -50,11 +50,6 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); - if (openApidoc.Components != null) - { - openApidoc.Workspace.RegisterComponents(openApidoc); - } - return openApidoc; } } From ea186d874ea55e2bb8324e78bcbf22fe577cef69 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 28 Mar 2024 21:02:18 +0300 Subject: [PATCH 17/34] Revert deleted code --- src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs index 959c9a35a..4c89d7796 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs @@ -18,6 +18,7 @@ namespace Microsoft.OpenApi.Services public class OpenApiReferenceResolver : OpenApiVisitorBase { private OpenApiDocument _currentDocument; + private readonly bool _resolveRemoteReferences; private List _errors = new(); /// From 6ebc49271b533f77e82be26a846569e4a5c0b16b Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Fri, 29 Mar 2024 13:39:56 +0300 Subject: [PATCH 18/34] Update reference test --- .../Models/References/OpenApiLinkReferenceTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs index 6ca010798..94dbdd0bd 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs @@ -107,6 +107,14 @@ public class OpenApiLinkReferenceTests parameters: userId: '$response.body#/id' description: The id value returned in the response can be used as the userId parameter in GET /users/{userId} + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string "; private readonly OpenApiLinkReference _localLinkReference; From 4f09473c592b38947cce1d7b0a93333142e4887f Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Fri, 29 Mar 2024 13:41:48 +0300 Subject: [PATCH 19/34] Update test --- .../ReferenceService/TryLoadReferenceV2Tests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index 0bbd5ea00..363151622 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.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.Collections.Generic; @@ -6,6 +6,7 @@ using FluentAssertions; using Json.Schema; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.ReferenceService @@ -138,6 +139,12 @@ public void LoadResponseAndSchemaReference() { Schema = new JsonSchemaBuilder() .Ref("#/definitions/SampleObject2") + .Description("Sample description") + .Required("name") + .Properties( + ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Build() } }, Reference = new() From 4e480eda07f9a6b0f1a077187c747d390fe3b450 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Fri, 29 Mar 2024 21:04:50 +0300 Subject: [PATCH 20/34] Update test file and test --- .../V2Tests/OpenApiDocumentTests.cs | 1 + .../Models/References/OpenApiResponseReferenceTest.cs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 748d441cc..4ab3e6986 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -165,6 +165,7 @@ public void ShouldAssignSchemaToAllResponses() .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier.")))); var errorSchema = new JsonSchemaBuilder() + .Ref("#/definitions/Error") .Properties(("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("fields", new JsonSchemaBuilder().Type(SchemaValueType.String))); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs index cab14f475..0f2fc2d2b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs @@ -56,6 +56,12 @@ public class OpenApiResponseReferenceTest text/plain: schema: $ref: '#/components/schemas/Pong' + schemas: + Pong: + type: object + properties: + sound: + type: string "; private readonly OpenApiResponseReference _localResponseReference; From 1b286e5e1ef355f816afe2071c080041a32cac9e Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 2 Apr 2024 12:52:24 +0300 Subject: [PATCH 21/34] Update models --- .../Models/OpenApiDocument.cs | 10 +-- .../Services/OpenApiWorkspace.cs | 80 +++++++++---------- 2 files changed, 41 insertions(+), 49 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 2ffd67361..a54c5f09e 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using Json.Schema; -using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Services; @@ -503,7 +502,7 @@ public JsonSchema ResolveJsonSchemaReference(Uri referenceUri) ? (JsonSchema)Workspace.ResolveReference(reference.Id, reference.Type, Components) // local ref : Workspace.ResolveReference(reference); // external ref - return resolvedSchema ?? throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); + return resolvedSchema; } /// @@ -553,11 +552,11 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool // Todo: Verify if we need to check to see if this external reference is actually targeted at this document. if (useExternal) { - if (this.Workspace == null) + if (Workspace == null) { throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution); } - return this.Workspace.ResolveReference(reference); + return Workspace.ResolveReference(reference); } if (!reference.Type.HasValue) @@ -580,8 +579,7 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool return null; } - return Workspace.ResolveReference(reference.Id, reference.Type, Components) - ?? throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); + return Workspace.ResolveReference(reference.Id, reference.Type, Components); } /// diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index a772eb3cf..a6f3adfb3 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Json.Schema; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Extensions; @@ -18,10 +17,10 @@ namespace Microsoft.OpenApi.Services /// public class OpenApiWorkspace { - private readonly Dictionary _documents = new(); - private readonly Dictionary _fragments = new(); - private readonly Dictionary _schemaFragments = new(); - private readonly Dictionary _artifacts = new(); + private readonly Dictionary _documentsRegistry = new(); + private readonly Dictionary _fragmentsRegistry = new(); + private readonly Dictionary _schemaFragmentsRegistry = new(); + private readonly Dictionary _artifactsRegistry = new(); /// /// A list of OpenApiDocuments contained in the workspace @@ -30,7 +29,7 @@ public IEnumerable Documents { get { - return _documents.Values; + return _documentsRegistry.Values; } } @@ -71,11 +70,6 @@ public OpenApiWorkspace() /// public OpenApiWorkspace(OpenApiWorkspace workspace) { } - /// - /// - /// - public IDictionary ComponentsRegistry { get; } = new Dictionary(); - /// /// Verify if workspace contains a document based on its URL. /// @@ -84,7 +78,7 @@ public OpenApiWorkspace(OpenApiWorkspace workspace) { } public bool Contains(string location) { var key = ToLocationUrl(location); - return _documents.ContainsKey(key) || _fragments.ContainsKey(key) || _artifacts.ContainsKey(key) || _schemaFragments.ContainsKey(key); + return _documentsRegistry.ContainsKey(key) || _fragmentsRegistry.ContainsKey(key) || _artifactsRegistry.ContainsKey(key) || _schemaFragmentsRegistry.ContainsKey(key); } /// @@ -97,9 +91,9 @@ public void AddDocument(string location, OpenApiDocument document) document.Workspace = this; var locationUrl = ToLocationUrl(location); - if (!_documents.ContainsKey(locationUrl)) + if (!_documentsRegistry.ContainsKey(locationUrl)) { - _documents.Add(locationUrl, document); + _documentsRegistry.Add(locationUrl, document); } } @@ -113,7 +107,7 @@ public void AddDocument(string location, OpenApiDocument document) /// public void AddFragment(string location, IOpenApiReferenceable fragment) { - _fragments.Add(ToLocationUrl(location), fragment); + _fragmentsRegistry.Add(ToLocationUrl(location), fragment); } /// @@ -124,20 +118,20 @@ public void AddFragment(string location, IOpenApiReferenceable fragment) public void AddSchemaFragment(string location, JsonSchema fragment) { var locationUri = ToLocationUrl(location); - if (!_schemaFragments.ContainsKey(locationUri)) + if (!_schemaFragmentsRegistry.ContainsKey(locationUri)) { - _schemaFragments.Add(locationUri, fragment); + _schemaFragmentsRegistry.Add(locationUri, fragment); } } /// - /// Add a stream based artificat to the workspace. Useful for images, examples, alternative schemas. + /// Add a stream based artifact to the workspace. Useful for images, examples, alternative schemas. /// /// /// public void AddArtifact(string location, Stream artifact) { - _artifacts.Add(ToLocationUrl(location), artifact); + _artifactsRegistry.Add(ToLocationUrl(location), artifact); } /// @@ -149,21 +143,20 @@ public void AddArtifact(string location, Stream artifact) public T ResolveReference(OpenApiReference reference) { var uri = new Uri(BaseUrl, reference.ExternalResource); - if (_documents.TryGetValue(uri, out var doc)) + if (_documentsRegistry.TryGetValue(uri, out var doc)) { return ResolveReference(reference.Id, reference.Type, doc.Components); } - else if (_fragments.TryGetValue(uri, out var fragment)) + else if (_fragmentsRegistry.TryGetValue(uri, out var fragment)) { var jsonPointer = new JsonPointer($"/{reference.Id ?? string.Empty}"); return (T)fragment.ResolveReference(jsonPointer); } - else if (_schemaFragments.TryGetValue(uri, out var schemaFragment)) + else if (_schemaFragmentsRegistry.TryGetValue(uri, out var schemaFragment)) { return (T)(schemaFragment as IBaseDocument); } return default; - } /// @@ -180,20 +173,27 @@ public T ResolveReference(string referenceId, ReferenceType? referenceType, O if (string.IsNullOrEmpty(referenceId)) return default; if (components == null) return default; - return referenceType switch + try { - ReferenceType.PathItem => (T)(IOpenApiReferenceable)components.PathItems[referenceId], - ReferenceType.Response => (T)(IOpenApiReferenceable)components.Responses[referenceId], - ReferenceType.Parameter => (T)(IOpenApiReferenceable)components.Parameters[referenceId], - ReferenceType.Example => (T)(IOpenApiReferenceable)components.Examples[referenceId], - ReferenceType.RequestBody => (T)(IOpenApiReferenceable)components.RequestBodies[referenceId], - ReferenceType.Header => (T)(IOpenApiReferenceable)components.Headers[referenceId], - ReferenceType.SecurityScheme => (T)(IOpenApiReferenceable)components.SecuritySchemes[referenceId], - ReferenceType.Link => (T)(IOpenApiReferenceable)components.Links[referenceId], - ReferenceType.Callback => (T)(IOpenApiReferenceable)components.Callbacks[referenceId], - ReferenceType.Schema => (T)(IBaseDocument)components.Schemas[referenceId], - _ => throw new OpenApiException(Properties.SRResource.InvalidReferenceType), - }; + return referenceType switch + { + ReferenceType.PathItem => (T)(IOpenApiReferenceable)components.PathItems[referenceId], + ReferenceType.Response => (T)(IOpenApiReferenceable)components.Responses[referenceId], + ReferenceType.Parameter => (T)(IOpenApiReferenceable)components.Parameters[referenceId], + ReferenceType.Example => (T)(IOpenApiReferenceable)components.Examples[referenceId], + ReferenceType.RequestBody => (T)(IOpenApiReferenceable)components.RequestBodies[referenceId], + ReferenceType.Header => (T)(IOpenApiReferenceable)components.Headers[referenceId], + ReferenceType.SecurityScheme => (T)(IOpenApiReferenceable)components.SecuritySchemes[referenceId], + ReferenceType.Link => (T)(IOpenApiReferenceable)components.Links[referenceId], + ReferenceType.Callback => (T)(IOpenApiReferenceable)components.Callbacks[referenceId], + ReferenceType.Schema => (T)(IBaseDocument)components.Schemas[referenceId], + _ => throw new OpenApiException(Properties.SRResource.InvalidReferenceType) + }; + } + catch (KeyNotFoundException) + { + throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); + } } /// @@ -203,18 +203,12 @@ public T ResolveReference(string referenceId, ReferenceType? referenceType, O /// public Stream GetArtifact(string location) { - return _artifacts[ToLocationUrl(location)]; + return _artifactsRegistry[ToLocationUrl(location)]; } private Uri ToLocationUrl(string location) { return new(BaseUrl, location); } - - private static JsonSchema FetchSchemaFromRegistry(Uri reference) - { - var resolvedSchema = (JsonSchema)SchemaRegistry.Global.Get(reference); - return resolvedSchema; - } } } From e7ff3608f9a69a0fbcdd709c23ddf55a787dc550 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 2 Apr 2024 12:52:49 +0300 Subject: [PATCH 22/34] Update tests --- .../V2Tests/ComparisonTests.cs | 7 ++++-- .../V2Tests/OpenApiDocumentTests.cs | 6 ++++- .../V31Tests/OpenApiDocumentTests.cs | 22 ++++++------------- .../V3Tests/OpenApiDocumentTests.cs | 13 ++++++----- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs index 5df1291bd..b555f7b77 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs @@ -4,6 +4,7 @@ using System.IO; using FluentAssertions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Reader; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -17,14 +18,16 @@ public class ComparisonTests [InlineData("minimal")] [InlineData("basic")] //[InlineData("definitions")] //Currently broken due to V3 references not behaving the same as V2 - public void EquivalentV2AndV3DocumentsShouldProductEquivalentObjects(string fileName) + public void EquivalentV2AndV3DocumentsShouldProduceEquivalentObjects(string fileName) { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); using var streamV2 = Resources.GetStream(Path.Combine(SampleFolderPath, $"{fileName}.v2.yaml")); using var streamV3 = Resources.GetStream(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml")); var result1 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, $"{fileName}.v2.yaml")); var result2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml")); - result2.OpenApiDocument.Should().BeEquivalentTo(result1.OpenApiDocument); + result2.OpenApiDocument.Should().BeEquivalentTo(result1.OpenApiDocument, + options => options.Excluding(x => x.Workspace)); result1.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(result2.OpenApiDiagnostic.Errors); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 4ab3e6986..382b79f33 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -2,12 +2,15 @@ // Licensed under the MIT license. using System; +using System.Globalization; using System.IO; using System.Linq; using FluentAssertions; using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Writers; +using VerifyXunit; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -147,7 +150,8 @@ public void ShouldParseProducesInAnyOrder() ["Error"] = errorSchema } } - }); + }, options => options.Excluding(x => x.Workspace)); + } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index 0bdfea92e..e1a7edbb6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -17,6 +17,11 @@ public class OpenApiDocumentTests { private const string SampleFolderPath = "V31Tests/Samples/OpenApiDocument/"; + public OpenApiDocumentTests() + { + OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); + } + public static T Clone(T element) where T : IOpenApiSerializable { using var stream = new MemoryStream(); @@ -179,7 +184,7 @@ public void ParseDocumentWithWebhooksShouldSucceed() // Assert var schema = actual.OpenApiDocument.Webhooks["/pets"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; actual.OpenApiDiagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); - actual.OpenApiDocument.Should().BeEquivalentTo(expected); + actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace)); } [Fact] @@ -320,24 +325,11 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() }; // Assert - actual.OpenApiDocument.Should().BeEquivalentTo(expected); + actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace)); actual.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); } - [Fact] - public void ParseDocumentWithDescriptionInDollarRefsShouldSucceed() - { - // Arrange - var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "documentWithSummaryAndDescriptionInReference.yaml")); - - // Act - var header = actual.OpenApiDocument.Components.Responses["Test"].Headers["X-Test"]; - - // Assert - Assert.True(header.Description == "A referenced X-Test header"); /*response header #ref's description overrides the header's description*/ - } - [Fact] public void ParseDocumentWithExampleInSchemaShouldSucceed() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index d7b038830..af12495f0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -94,7 +94,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() Version = "0.9.1" }, Paths = new OpenApiPaths() - }); + }, options => options.Excluding(x => x.Workspace)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() @@ -145,7 +145,7 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() } }, Paths = new OpenApiPaths() - }); + }, options => options.Excluding(x => x.Workspace)); } [Fact] public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() @@ -161,7 +161,7 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() Version = "0.9" }, Paths = new OpenApiPaths() - }); + }, options => options.Excluding(x => x.Workspace)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic @@ -189,7 +189,7 @@ public void ParseMinimalDocumentShouldSucceed() Version = "0.9.1" }, Paths = new OpenApiPaths() - }); + }, options => options.Excluding(x => x.Workspace)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() @@ -510,7 +510,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Components = components }; - result.OpenApiDocument.Should().BeEquivalentTo(expectedDoc); + result.OpenApiDocument.Should().BeEquivalentTo(expectedDoc, options => options.Excluding(x => x.Workspace)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); @@ -943,7 +943,8 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; - actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(m => m.Name == "HostDocument")); + actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(m => m.Name == "HostDocument") + .Excluding(x => x.Workspace)); actual.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); From ef633cf8114319bed28415c8f26e66f7dd395474 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 2 Apr 2024 12:53:07 +0300 Subject: [PATCH 23/34] Update Public Api --- .../PublicApi/PublicApi.approved.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 88fb6b3c0..7a140ad04 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -623,6 +623,7 @@ namespace Microsoft.OpenApi.Models public System.Collections.Generic.IDictionary Webhooks { get; set; } public Microsoft.OpenApi.Services.OpenApiWorkspace Workspace { get; set; } public Json.Schema.JsonSchema FindSubschema(Json.Pointer.JsonPointer pointer, Json.Schema.EvaluationOptions options) { } + public Json.Schema.JsonSchema ResolveJsonSchemaReference(System.Uri referenceUri) { } public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } public System.Collections.Generic.IEnumerable ResolveReferences() { } public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } @@ -1397,6 +1398,7 @@ namespace Microsoft.OpenApi.Services public OpenApiWorkspace(System.Uri baseUrl) { } public System.Collections.Generic.IEnumerable Artifacts { get; } public System.Uri BaseUrl { get; } + public System.Collections.Generic.IDictionary ComponentsRegistry { get; } public System.Collections.Generic.IEnumerable Documents { get; } public System.Collections.Generic.IEnumerable Fragments { get; } public void AddArtifact(string location, System.IO.Stream artifact) { } @@ -1405,8 +1407,8 @@ namespace Microsoft.OpenApi.Services public void AddSchemaFragment(string location, Json.Schema.JsonSchema fragment) { } public bool Contains(string location) { } public System.IO.Stream GetArtifact(string location) { } - public Json.Schema.JsonSchema ResolveJsonSchemaReference(System.Uri reference) { } - public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } + public T ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } + public T ResolveReference(string referenceId, Microsoft.OpenApi.Models.ReferenceType? referenceType, Microsoft.OpenApi.Models.OpenApiComponents components) { } } public class OperationSearch : Microsoft.OpenApi.Services.OpenApiVisitorBase { From dbfcf0ab20cfc5bd82974cd6db1659314941b14a Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 2 Apr 2024 13:06:04 +0300 Subject: [PATCH 24/34] Resolve public Api --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 7a140ad04..f4f7a3503 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1398,7 +1398,6 @@ namespace Microsoft.OpenApi.Services public OpenApiWorkspace(System.Uri baseUrl) { } public System.Collections.Generic.IEnumerable Artifacts { get; } public System.Uri BaseUrl { get; } - public System.Collections.Generic.IDictionary ComponentsRegistry { get; } public System.Collections.Generic.IEnumerable Documents { get; } public System.Collections.Generic.IEnumerable Fragments { get; } public void AddArtifact(string location, System.IO.Stream artifact) { } From 464bd218c36add1264d2ca5b950b57bcfed43d52 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 2 Apr 2024 16:10:07 +0300 Subject: [PATCH 25/34] Register local refs within external documents with unique GUID --- .../OpenApiRemoteReferenceCollector.cs | 17 +++++++++-- .../Reader/Services/OpenApiWorkspaceLoader.cs | 29 +++++++++++++------ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs index 135e69eee..f1af4db56 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -14,6 +15,7 @@ namespace Microsoft.OpenApi.Reader.Services internal class OpenApiRemoteReferenceCollector : OpenApiVisitorBase { private readonly Dictionary _references = new(); + private Guid _guid = new(); /// /// List of external references collected from OpenApiDocument @@ -32,13 +34,14 @@ public IEnumerable References /// public override void Visit(IOpenApiReferenceable referenceable) { - AddReference(referenceable.Reference); + AddExternalReference(referenceable.Reference); + AddLocalReference(referenceable.Reference); } /// /// Collect external reference /// - private void AddReference(OpenApiReference reference) + private void AddExternalReference(OpenApiReference reference) { if (reference is {IsExternal: true} && !_references.ContainsKey(reference.ExternalResource)) @@ -46,5 +49,15 @@ private void AddReference(OpenApiReference reference) _references.Add(reference.ExternalResource, reference); } } + + private void AddLocalReference(OpenApiReference reference) + { + if (reference is { IsExternal: false } && + !_references.ContainsKey(reference.ReferenceV3)) + { + reference.ExternalResource = _guid.ToString(); + _references.Add(reference.ReferenceV3, reference); + } + } } } diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs index d6389d2fb..24d932ddd 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs @@ -39,21 +39,32 @@ internal async Task LoadAsync(OpenApiReference reference, // Walk references foreach (var item in referenceCollector.References) { + // If not already in workspace, load it and process references if (!_workspace.Contains(item.ExternalResource)) { - var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute)); - var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken); - // Merge diagnostics - if (result.OpenApiDiagnostic != null) + if (!Guid.TryParse(item.ExternalResource, out _)) { - diagnostic.AppendDiagnostic(result.OpenApiDiagnostic, item.ExternalResource); + var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute)); + var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken); + // Merge diagnostics + if (result.OpenApiDiagnostic != null) + { + diagnostic.AppendDiagnostic(result.OpenApiDiagnostic, item.ExternalResource); + } + if (result.OpenApiDocument != null) + { + var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken); + diagnostic = loadDiagnostic; + } } - if (result.OpenApiDocument != null) + else // local ref in an external file, add this to the documents registry { - var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken); - diagnostic = loadDiagnostic; - } + if (!_workspace.Contains(item.ExternalResource)) + { + _workspace.AddDocument(reference.ExternalResource, document); + } + } } } From 77d1e493ea7cccb002ca6ea893bfb9152883af49 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Tue, 2 Apr 2024 16:29:19 +0300 Subject: [PATCH 26/34] Refactor functions --- .../Services/OpenApiRemoteReferenceCollector.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs index f1af4db56..343c59e41 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs @@ -18,7 +18,7 @@ internal class OpenApiRemoteReferenceCollector : OpenApiVisitorBase private Guid _guid = new(); /// - /// List of external references collected from OpenApiDocument + /// List of all internal and external references collected from OpenApiDocument /// public IEnumerable References { @@ -34,24 +34,22 @@ public IEnumerable References /// public override void Visit(IOpenApiReferenceable referenceable) { - AddExternalReference(referenceable.Reference); - AddLocalReference(referenceable.Reference); + AddReferences(referenceable.Reference); } /// - /// Collect external reference + /// Collect internal and external references /// - private void AddExternalReference(OpenApiReference reference) + private void AddReferences(OpenApiReference reference) { + // External refs if (reference is {IsExternal: true} && !_references.ContainsKey(reference.ExternalResource)) { _references.Add(reference.ExternalResource, reference); } - } - private void AddLocalReference(OpenApiReference reference) - { + // Local refs if (reference is { IsExternal: false } && !_references.ContainsKey(reference.ReferenceV3)) { From 9a9827a884d876338ac543d16a89d58c6ef5a6c8 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Wed, 3 Apr 2024 03:30:03 +0300 Subject: [PATCH 27/34] Use unique document ids in URIs in components registry and ref resolution --- .../Models/OpenApiConstants.cs | 5 + .../Models/OpenApiDocument.cs | 54 ++--- .../OpenApiRemoteReferenceCollector.cs | 14 +- .../Reader/Services/OpenApiWorkspaceLoader.cs | 31 +-- .../Reader/V2/OpenApiDocumentDeserializer.cs | 25 +-- .../Reader/V3/OpenApiDocumentDeserializer.cs | 13 +- .../Reader/V31/OpenApiDocumentDeserializer.cs | 13 +- .../OpenApiComponentsRegistryExtensions.cs | 88 ++++++++ .../Services/OpenApiWorkspace.cs | 200 +++++++----------- .../OpenApiDiagnosticTests.cs | 1 - .../OpenApiWorkspaceStreamTests.cs | 1 - .../V2Tests/ComparisonTests.cs | 2 +- .../V2Tests/OpenApiDocumentTests.cs | 2 +- .../V31Tests/OpenApiDocumentTests.cs | 5 +- .../V3Tests/OpenApiDocumentTests.cs | 12 +- .../GraphTests.cs | 4 - .../OpenApiCallbackReferenceTests.cs | 4 +- .../OpenApiExampleReferenceTests.cs | 4 +- .../References/OpenApiHeaderReferenceTests.cs | 4 +- .../References/OpenApiLinkReferenceTests.cs | 4 +- .../OpenApiParameterReferenceTests.cs | 4 +- .../OpenApiPathItemReferenceTests.cs | 4 +- .../OpenApiRequestBodyReferenceTests.cs | 4 +- .../OpenApiResponseReferenceTest.cs | 4 +- .../PublicApi/PublicApi.approved.txt | 16 +- .../Workspaces/OpenApiWorkspaceTests.cs | 85 ++------ 26 files changed, 299 insertions(+), 304 deletions(-) create mode 100644 src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 107d9cc15..3db125b37 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -625,6 +625,11 @@ public static class OpenApiConstants /// public const string V2ReferenceUri = "https://registry/definitions/"; + /// + /// The default registry uri for OpenApi documents and workspaces + /// + public const string BaseRegistryUri = "http://openapi.net/"; + #region V2.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index a54c5f09e..a88ff3bae 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -5,12 +5,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using Json.Schema; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Services; @@ -97,7 +97,7 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo public OpenApiDocument() { Workspace = new OpenApiWorkspace(); - Workspace.AddDocument("/", this); + BaseUri = new(OpenApiConstants.BaseRegistryUri + Guid.NewGuid().ToString()); } /// @@ -488,21 +488,24 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) /// /// A JsonSchema ref. public JsonSchema ResolveJsonSchemaReference(Uri referenceUri) - { - if (referenceUri == null) return null; - - OpenApiReference reference = new OpenApiReference() + { + string uriLocation; + string id = referenceUri.OriginalString.Split('/')?.Last(); + string relativePath = "/components/" + ReferenceType.Schema.GetDisplayName() + "/" + id; + if (referenceUri.OriginalString.StartsWith("#")) { - ExternalResource = referenceUri.OriginalString, - Id = referenceUri.OriginalString.Split('/').Last(), - Type = ReferenceType.Schema - }; - - JsonSchema resolvedSchema = reference.ExternalResource.StartsWith("#") - ? (JsonSchema)Workspace.ResolveReference(reference.Id, reference.Type, Components) // local ref - : Workspace.ResolveReference(reference); // external ref + // Local reference + uriLocation = BaseUri + relativePath; + } + else + { + // External reference + var externalUri = referenceUri.OriginalString.Split('#').First(); + var externalDocId = Workspace.GetDocumentId(externalUri); + uriLocation = externalDocId + relativePath; + } - return resolvedSchema; + return (JsonSchema)Workspace.ResolveReference(uriLocation); } /// @@ -549,16 +552,6 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool return null; } - // Todo: Verify if we need to check to see if this external reference is actually targeted at this document. - if (useExternal) - { - if (Workspace == null) - { - throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution); - } - return Workspace.ResolveReference(reference); - } - if (!reference.Type.HasValue) { throw new ArgumentException(Properties.SRResource.LocalReferenceRequiresType); @@ -579,9 +572,16 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool return null; } - return Workspace.ResolveReference(reference.Id, reference.Type, Components); - } + string uriLocation; + string relativePath = "/components/" + reference.Type.GetDisplayName() + "/" + reference.Id; + + uriLocation = useExternal + ? Workspace.GetDocumentId(reference.ExternalResource)?.OriginalString + relativePath + : BaseUri + relativePath; + return Workspace.ResolveReference(uriLocation); + } + /// /// Parses a local file path or Url into an Open API document. /// diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs index 343c59e41..6a80941a5 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs @@ -15,7 +15,6 @@ namespace Microsoft.OpenApi.Reader.Services internal class OpenApiRemoteReferenceCollector : OpenApiVisitorBase { private readonly Dictionary _references = new(); - private Guid _guid = new(); /// /// List of all internal and external references collected from OpenApiDocument @@ -34,28 +33,19 @@ public IEnumerable References /// public override void Visit(IOpenApiReferenceable referenceable) { - AddReferences(referenceable.Reference); + AddExternalReferences(referenceable.Reference); } /// /// Collect internal and external references /// - private void AddReferences(OpenApiReference reference) + private void AddExternalReferences(OpenApiReference reference) { - // External refs if (reference is {IsExternal: true} && !_references.ContainsKey(reference.ExternalResource)) { _references.Add(reference.ExternalResource, reference); } - - // Local refs - if (reference is { IsExternal: false } && - !_references.ContainsKey(reference.ReferenceV3)) - { - reference.ExternalResource = _guid.ToString(); - _references.Add(reference.ReferenceV3, reference); - } } } } diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs index 24d932ddd..c7a7e6e40 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs @@ -26,7 +26,8 @@ internal async Task LoadAsync(OpenApiReference reference, OpenApiDiagnostic diagnostic = null, CancellationToken cancellationToken = default) { - _workspace.AddDocument(reference.ExternalResource, document); + _workspace.AddDocumentId(reference.ExternalResource, document.BaseUri); + _workspace.RegisterComponents(document); document.Workspace = _workspace; // Collect remote references by walking document @@ -43,28 +44,18 @@ internal async Task LoadAsync(OpenApiReference reference, // If not already in workspace, load it and process references if (!_workspace.Contains(item.ExternalResource)) { - if (!Guid.TryParse(item.ExternalResource, out _)) + var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute)); + var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken); + // Merge diagnostics + if (result.OpenApiDiagnostic != null) { - var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute)); - var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken); - // Merge diagnostics - if (result.OpenApiDiagnostic != null) - { - diagnostic.AppendDiagnostic(result.OpenApiDiagnostic, item.ExternalResource); - } - if (result.OpenApiDocument != null) - { - var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken); - diagnostic = loadDiagnostic; - } + diagnostic.AppendDiagnostic(result.OpenApiDiagnostic, item.ExternalResource); } - else // local ref in an external file, add this to the documents registry + if (result.OpenApiDocument != null) { - if (!_workspace.Contains(item.ExternalResource)) - { - _workspace.AddDocument(reference.ExternalResource, document); - } - } + var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken); + diagnostic = loadDiagnostic; + } } } diff --git a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs index c5f0d5f8f..b9a5447ab 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs @@ -236,41 +236,38 @@ private static string BuildUrl(string scheme, string host, string basePath) public static OpenApiDocument LoadOpenApi(RootNode rootNode) { - var openApidoc = new OpenApiDocument(); + var openApiDoc = new OpenApiDocument(); var openApiNode = rootNode.GetMap(); - ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); + ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); - if (openApidoc.Paths != null) + if (openApiDoc.Paths != null) { ProcessResponsesMediaTypes( rootNode.GetMap(), - openApidoc.Paths.Values + openApiDoc.Paths.Values .SelectMany(path => path.Operations?.Values ?? Enumerable.Empty()) .SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty()), openApiNode.Context); } - ProcessResponsesMediaTypes(rootNode.GetMap(), openApidoc.Components?.Responses?.Values, openApiNode.Context); + ProcessResponsesMediaTypes(rootNode.GetMap(), openApiDoc.Components?.Responses?.Values, openApiNode.Context); // Post Process OpenApi Object - if (openApidoc.Servers == null) + if (openApiDoc.Servers == null) { - openApidoc.Servers = new List(); + openApiDoc.Servers = new List(); } - MakeServers(openApidoc.Servers, openApiNode.Context, rootNode); + MakeServers(openApiDoc.Servers, openApiNode.Context, rootNode); - FixRequestBodyReferences(openApidoc); + FixRequestBodyReferences(openApiDoc); // Register components - //if (openApidoc.Components != null) - //{ - // openApidoc.Workspace.RegisterComponents(openApidoc.BaseUri, openApidoc.Components); - //} + openApiDoc.Workspace.RegisterComponents(openApiDoc); - return openApidoc; + return openApiDoc; } private static void ProcessResponsesMediaTypes(MapNode mapNode, IEnumerable responses, ParsingContext context) diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs index b94493f4c..274dc3010 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs @@ -4,6 +4,7 @@ using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Services; namespace Microsoft.OpenApi.Reader.V3 { @@ -46,17 +47,15 @@ internal static partial class OpenApiV3Deserializer public static OpenApiDocument LoadOpenApi(RootNode rootNode) { - var openApidoc = new OpenApiDocument(); + var openApiDoc = new OpenApiDocument(); var openApiNode = rootNode.GetMap(); - ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); + ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); - //if (openApidoc.Components != null) - //{ - // openApidoc.Workspace.RegisterComponents(openApidoc); - //} + // Register components + openApiDoc.Workspace.RegisterComponents(openApiDoc); - return openApidoc; + return openApiDoc; } } } diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs index 37d53dd73..069b47ddf 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs @@ -1,6 +1,8 @@ -using Microsoft.OpenApi.Extensions; +using System; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Reader.ParseNodes; +using Microsoft.OpenApi.Services; namespace Microsoft.OpenApi.Reader.V31 { @@ -45,12 +47,15 @@ internal static partial class OpenApiV31Deserializer public static OpenApiDocument LoadOpenApi(RootNode rootNode) { - var openApidoc = new OpenApiDocument(); + var openApiDoc = new OpenApiDocument(); var openApiNode = rootNode.GetMap(); - ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields); + ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); - return openApidoc; + // Register components + openApiDoc.Workspace.RegisterComponents(openApiDoc); + + return openApiDoc; } } } diff --git a/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs new file mode 100644 index 000000000..9f129c016 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + internal static class OpenApiComponentsRegistryExtensions + { + public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDocument document) + { + if (document?.Components == null) return; + + var baseUri = document.BaseUri + "/components/"; + + // Register Schema + foreach (var item in document.Components.Schemas) + { + var location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key; + workspace.RegisterComponent(location, item.Value); + } + + // Register Parameters + foreach (var item in document.Components.Parameters) + { + var 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; + workspace.RegisterComponent(location, item.Value); + } + + // Register RequestBodies + foreach (var item in document.Components.RequestBodies) + { + var 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; + workspace.RegisterComponent(location, item.Value); + } + + // Register Callbacks + foreach (var item in document.Components.Callbacks) + { + var 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; + workspace.RegisterComponent(location, item.Value); + } + + // Register Examples + foreach (var item in document.Components.Examples) + { + var 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; + workspace.RegisterComponent(location, item.Value); + } + + // Register SecuritySchemes + foreach (var item in document.Components.SecuritySchemes) + { + var location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key; + workspace.RegisterComponent(location, item.Value); + } + } + } +} diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index a6f3adfb3..ca3fb32d0 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -5,8 +5,6 @@ using System.Collections.Generic; using System.IO; using Json.Schema; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; @@ -17,37 +15,16 @@ namespace Microsoft.OpenApi.Services /// public class OpenApiWorkspace { - private readonly Dictionary _documentsRegistry = new(); - private readonly Dictionary _fragmentsRegistry = new(); - private readonly Dictionary _schemaFragmentsRegistry = new(); - private readonly Dictionary _artifactsRegistry = new(); - - /// - /// A list of OpenApiDocuments contained in the workspace - /// - public IEnumerable Documents - { - get - { - return _documentsRegistry.Values; - } - } - - /// - /// A list of document fragments that are contained in the workspace - /// - public IEnumerable Fragments { get; } + private readonly Dictionary _documentsIdRegistry = new(); + private readonly Dictionary _artifactsRegistry = new(); + private readonly Dictionary _jsonSchemaRegistry = new(); + private readonly Dictionary _IOpenApiReferenceableRegistry = new(); /// /// The base location from where all relative references are resolved /// public Uri BaseUrl { get; } - - /// - /// A list of document fragments that are contained in the workspace - /// - public IEnumerable Artifacts { get; } - + /// /// Initialize workspace pointing to a base URL to allow resolving relative document locations. Use a file:// url to point to a folder /// @@ -62,7 +39,7 @@ public OpenApiWorkspace(Uri baseUrl) /// public OpenApiWorkspace() { - BaseUrl = new("file://" + Environment.CurrentDirectory + $"{Path.DirectorySeparatorChar}"); + BaseUrl = new Uri(OpenApiConstants.BaseRegistryUri); } /// @@ -71,139 +48,114 @@ public OpenApiWorkspace() public OpenApiWorkspace(OpenApiWorkspace workspace) { } /// - /// Verify if workspace contains a document based on its URL. + /// Returns the total count of all the components in the workspace registry /// - /// A relative or absolute URL of the file. Use file:// for folder locations. - /// Returns true if a matching document is found. - public bool Contains(string location) + /// + public int ComponentsCount() { - var key = ToLocationUrl(location); - return _documentsRegistry.ContainsKey(key) || _fragmentsRegistry.ContainsKey(key) || _artifactsRegistry.ContainsKey(key) || _schemaFragmentsRegistry.ContainsKey(key); + return _IOpenApiReferenceableRegistry.Count + _jsonSchemaRegistry.Count + _artifactsRegistry.Count; } /// - /// Add an OpenApiDocument to the workspace. + /// Registers a component in the component registry. /// - /// The string location. - /// The OpenAPI document. - public void AddDocument(string location, OpenApiDocument document) + /// + /// + /// true if the component is successfully registered; otherwise false. + public bool RegisterComponent(string location, T component) { - document.Workspace = this; - var locationUrl = ToLocationUrl(location); - - if (!_documentsRegistry.ContainsKey(locationUrl)) + var uri = ToLocationUrl(location); + if (component is IBaseDocument schema) { - _documentsRegistry.Add(locationUrl, document); + if (!_jsonSchemaRegistry.ContainsKey(uri)) + { + _jsonSchemaRegistry[uri] = schema; + return true; + } } + else if (component is IOpenApiReferenceable referenceable) + { + if (!_IOpenApiReferenceableRegistry.ContainsKey(uri)) + { + _IOpenApiReferenceableRegistry[uri] = referenceable; + return true; + } + } + else if (component is Stream stream) + { + if (!_artifactsRegistry.ContainsKey(uri)) + { + _artifactsRegistry[uri] = stream; + return true; + } + } + + return false; } /// - /// Adds a fragment of an OpenApiDocument to the workspace. + /// Adds a document id to the dictionaries of document locations and their ids. /// - /// - /// - /// Not sure how this is going to work. Does the reference just point to the fragment as a whole, or do we need to - /// to be able to point into the fragment. Keeping it private until we figure it out. - /// - public void AddFragment(string location, IOpenApiReferenceable fragment) + /// + /// + public void AddDocumentId(string key, Uri value) { - _fragmentsRegistry.Add(ToLocationUrl(location), fragment); + if (!_documentsIdRegistry.ContainsKey(key)) + { + _documentsIdRegistry[key] = value; + } } /// - /// Adds a schema fragment of an OpenApiDocument to the workspace. + /// Retrieves the document id given a key. /// - /// - /// - public void AddSchemaFragment(string location, JsonSchema fragment) + /// + /// The document id of the given key. + public Uri GetDocumentId(string key) { - var locationUri = ToLocationUrl(location); - if (!_schemaFragmentsRegistry.ContainsKey(locationUri)) + if (_documentsIdRegistry.TryGetValue(key, out var id)) { - _schemaFragmentsRegistry.Add(locationUri, fragment); - } + return id; + } + return null; } /// - /// Add a stream based artifact to the workspace. Useful for images, examples, alternative schemas. + /// Verify if workspace contains a component based on its URL. /// - /// - /// - public void AddArtifact(string location, Stream artifact) + /// A relative or absolute URL of the file. Use file:// for folder locations. + /// Returns true if a matching document is found. + public bool Contains(string location) { - _artifactsRegistry.Add(ToLocationUrl(location), artifact); + var key = ToLocationUrl(location); + return _IOpenApiReferenceableRegistry.ContainsKey(key) || _jsonSchemaRegistry.ContainsKey(key) || _artifactsRegistry.ContainsKey(key); } /// - /// Returns the target of a referenceable item from within the workspace. + /// Resolves a reference given a key. /// /// - /// - /// - public T ResolveReference(OpenApiReference reference) + /// + /// The resolved reference. + public T ResolveReference(string location) { - var uri = new Uri(BaseUrl, reference.ExternalResource); - if (_documentsRegistry.TryGetValue(uri, out var doc)) - { - return ResolveReference(reference.Id, reference.Type, doc.Components); - } - else if (_fragmentsRegistry.TryGetValue(uri, out var fragment)) + if (string.IsNullOrEmpty(location)) return default; + + var uri = ToLocationUrl(location); + if (_IOpenApiReferenceableRegistry.TryGetValue(uri, out var referenceableValue)) { - var jsonPointer = new JsonPointer($"/{reference.Id ?? string.Empty}"); - return (T)fragment.ResolveReference(jsonPointer); + return (T)referenceableValue; } - else if (_schemaFragmentsRegistry.TryGetValue(uri, out var schemaFragment)) + else if (_jsonSchemaRegistry.TryGetValue(uri, out var schemaValue)) { - return (T)(schemaFragment as IBaseDocument); + return (T)schemaValue; } - return default; - } - - /// - /// - /// - /// - /// - /// - /// - /// - /// - public T ResolveReference(string referenceId, ReferenceType? referenceType, OpenApiComponents components) - { - if (string.IsNullOrEmpty(referenceId)) return default; - if (components == null) return default; - - try + else if (_artifactsRegistry.TryGetValue(uri, out var artifact)) { - return referenceType switch - { - ReferenceType.PathItem => (T)(IOpenApiReferenceable)components.PathItems[referenceId], - ReferenceType.Response => (T)(IOpenApiReferenceable)components.Responses[referenceId], - ReferenceType.Parameter => (T)(IOpenApiReferenceable)components.Parameters[referenceId], - ReferenceType.Example => (T)(IOpenApiReferenceable)components.Examples[referenceId], - ReferenceType.RequestBody => (T)(IOpenApiReferenceable)components.RequestBodies[referenceId], - ReferenceType.Header => (T)(IOpenApiReferenceable)components.Headers[referenceId], - ReferenceType.SecurityScheme => (T)(IOpenApiReferenceable)components.SecuritySchemes[referenceId], - ReferenceType.Link => (T)(IOpenApiReferenceable)components.Links[referenceId], - ReferenceType.Callback => (T)(IOpenApiReferenceable)components.Callbacks[referenceId], - ReferenceType.Schema => (T)(IBaseDocument)components.Schemas[referenceId], - _ => throw new OpenApiException(Properties.SRResource.InvalidReferenceType) - }; + return (T)(object)artifact; } - catch (KeyNotFoundException) - { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); - } - } - /// - /// - /// - /// - /// - public Stream GetArtifact(string location) - { - return _artifactsRegistry[ToLocationUrl(location)]; + return default; } private Uri ToLocationUrl(string location) diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 05c40c21d..3efaf0150 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -57,7 +57,6 @@ public async Task DiagnosticReportMergedForExternalReference() Assert.NotNull(result); Assert.NotNull(result.OpenApiDocument.Workspace); - Assert.True(result.OpenApiDocument.Workspace.Contains("TodoReference.yaml")); result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List { new OpenApiError("", "[File: ./TodoReference.yaml] Paths is a REQUIRED field at #/"), diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index ca1455014..d52934ec0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -66,7 +66,6 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings); Assert.NotNull(result.OpenApiDocument.Workspace); - Assert.True(result.OpenApiDocument.Workspace.Contains("TodoComponents.yaml")); var referencedSchema = result.OpenApiDocument .Paths["/todos"] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs index b555f7b77..61d3a4021 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs @@ -27,7 +27,7 @@ public void EquivalentV2AndV3DocumentsShouldProduceEquivalentObjects(string file var result2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml")); result2.OpenApiDocument.Should().BeEquivalentTo(result1.OpenApiDocument, - options => options.Excluding(x => x.Workspace)); + options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); result1.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(result2.OpenApiDiagnostic.Errors); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 382b79f33..43e97a2b6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -150,7 +150,7 @@ public void ShouldParseProducesInAnyOrder() ["Error"] = errorSchema } } - }, options => options.Excluding(x => x.Workspace)); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index e1a7edbb6..6bc6eb1d4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -184,7 +184,7 @@ public void ParseDocumentWithWebhooksShouldSucceed() // Assert var schema = actual.OpenApiDocument.Webhooks["/pets"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; actual.OpenApiDiagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); - actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace)); + actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); } [Fact] @@ -325,7 +325,8 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() }; // Assert - actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace)); + actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace) + .Excluding(y => y.BaseUri)); actual.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index af12495f0..688c5621f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -94,7 +94,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() Version = "0.9.1" }, Paths = new OpenApiPaths() - }, options => options.Excluding(x => x.Workspace)); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() @@ -145,7 +145,7 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() } }, Paths = new OpenApiPaths() - }, options => options.Excluding(x => x.Workspace)); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); } [Fact] public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() @@ -161,7 +161,7 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() Version = "0.9" }, Paths = new OpenApiPaths() - }, options => options.Excluding(x => x.Workspace)); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic @@ -189,7 +189,7 @@ public void ParseMinimalDocumentShouldSucceed() Version = "0.9.1" }, Paths = new OpenApiPaths() - }, options => options.Excluding(x => x.Workspace)); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() @@ -510,7 +510,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Components = components }; - result.OpenApiDocument.Should().BeEquivalentTo(expectedDoc, options => options.Excluding(x => x.Workspace)); + result.OpenApiDocument.Should().BeEquivalentTo(expectedDoc, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); @@ -944,7 +944,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() }; actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(m => m.Name == "HostDocument") - .Excluding(x => x.Workspace)); + .Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); actual.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); diff --git a/test/Microsoft.OpenApi.SmokeTests/GraphTests.cs b/test/Microsoft.OpenApi.SmokeTests/GraphTests.cs index 8e2344fc1..4527f1016 100644 --- a/test/Microsoft.OpenApi.SmokeTests/GraphTests.cs +++ b/test/Microsoft.OpenApi.SmokeTests/GraphTests.cs @@ -52,11 +52,7 @@ public GraphTests(ITestOutputHelper output) public void LoadOpen() { var operations = new[] { "foo", "bar" }; - var workspace = new OpenApiWorkspace(); - workspace.AddDocument(graphOpenApiUrl, _graphOpenApi); var subset = new OpenApiDocument(); - workspace.AddDocument("subset", subset); - Assert.NotNull(_graphOpenApi); } } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs index 0fbac322a..5b5be8385 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs @@ -9,6 +9,7 @@ using Microsoft.OpenApi.Models.References; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Writers; using VerifyXunit; using Xunit; @@ -136,7 +137,8 @@ public OpenApiCallbackReferenceTests() OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); OpenApiDocument openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; OpenApiDocument openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; - openApiDoc.Workspace.AddDocument("https://myserver.com/beta", openApiDoc_2); + openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", openApiDoc_2.BaseUri); + openApiDoc.Workspace.RegisterComponents(openApiDoc_2); _externalCallbackReference = new("callbackEvent", openApiDoc, "https://myserver.com/beta"); _localCallbackReference = new("callbackEvent", openApiDoc_2); } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs index 56c6c0c1d..8c24d7307 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs @@ -10,6 +10,7 @@ using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -115,7 +116,8 @@ public OpenApiExampleReferenceTests() OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); _localExampleReference = new OpenApiExampleReference("UserExample", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs index df438a6b9..6689a2ee6 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs @@ -11,6 +11,7 @@ using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -85,7 +86,8 @@ public OpenApiHeaderReferenceTests() OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); _localHeaderReference = new OpenApiHeaderReference("LocationHeader", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs index 94dbdd0bd..4e9d10c6b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs @@ -10,6 +10,7 @@ using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -127,7 +128,8 @@ public OpenApiLinkReferenceTests() OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); _localLinkReference = new("GetUserByUserId", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs index be0e7fa83..d5765d49f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs @@ -10,6 +10,7 @@ using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -85,7 +86,8 @@ public OpenApiParameterReferenceTests() OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); _localParameterReference = new("limitParam", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs index 8bbc05d16..a5b8e21d4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs @@ -10,6 +10,7 @@ using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -82,7 +83,8 @@ public OpenApiPathItemReferenceTests() OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); _localPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs index ea2fdb588..4c7fa36a9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs @@ -12,6 +12,7 @@ using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -92,7 +93,8 @@ public OpenApiRequestBodyReferenceTests() OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); _localRequestBodyReference = new("UserRequest", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs index 0f2fc2d2b..b77978b9d 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs @@ -11,6 +11,7 @@ using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -74,7 +75,8 @@ public OpenApiResponseReferenceTest() OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; - _openApiDoc.Workspace.AddDocument("https://myserver.com/beta", _openApiDoc_2); + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); _localResponseReference = new("OkResponse", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index f4f7a3503..3cc06d7f5 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -457,6 +457,7 @@ namespace Microsoft.OpenApi.Models public const string AuthorizationCode = "authorizationCode"; public const string AuthorizationUrl = "authorizationUrl"; public const string BasePath = "basePath"; + public const string BaseRegistryUri = "http://openapi.net/"; public const string Basic = "basic"; public const string Bearer = "bearer"; public const string BearerFormat = "bearerFormat"; @@ -1396,18 +1397,13 @@ namespace Microsoft.OpenApi.Services public OpenApiWorkspace() { } public OpenApiWorkspace(Microsoft.OpenApi.Services.OpenApiWorkspace workspace) { } public OpenApiWorkspace(System.Uri baseUrl) { } - public System.Collections.Generic.IEnumerable Artifacts { get; } public System.Uri BaseUrl { get; } - public System.Collections.Generic.IEnumerable Documents { get; } - public System.Collections.Generic.IEnumerable Fragments { get; } - public void AddArtifact(string location, System.IO.Stream artifact) { } - public void AddDocument(string location, Microsoft.OpenApi.Models.OpenApiDocument document) { } - public void AddFragment(string location, Microsoft.OpenApi.Interfaces.IOpenApiReferenceable fragment) { } - public void AddSchemaFragment(string location, Json.Schema.JsonSchema fragment) { } + public void AddDocumentId(string key, System.Uri value) { } + public int ComponentsCount() { } public bool Contains(string location) { } - public System.IO.Stream GetArtifact(string location) { } - public T ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } - public T ResolveReference(string referenceId, Microsoft.OpenApi.Models.ReferenceType? referenceType, Microsoft.OpenApi.Models.OpenApiComponents components) { } + public System.Uri GetDocumentId(string key) { } + public bool RegisterComponent(string location, T component) { } + public T ResolveReference(string location) { } } public class OperationSearch : Microsoft.OpenApi.Services.OpenApiVisitorBase { diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index 307132958..68cb9057a 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using Json.Schema; -using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; using Xunit; @@ -15,22 +14,9 @@ namespace Microsoft.OpenApi.Tests public class OpenApiWorkspaceTests { [Fact] - public void OpenApiWorkspaceCanHoldMultipleDocuments() + public void OpenApiWorkspacesCanAddComponentsFromAnotherDocument() { - var workspace = new OpenApiWorkspace(); - - workspace.AddDocument("root", new()); - workspace.AddDocument("common", new()); - - Assert.Equal(2, workspace.Documents.Count()); - } - - [Fact] - public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() - { - var workspace = new OpenApiWorkspace(); - - workspace.AddDocument("root", new OpenApiDocument() + var doc = new OpenApiDocument() { Paths = new OpenApiPaths() { @@ -57,8 +43,9 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() } } } - }); - workspace.AddDocument("common", new OpenApiDocument() + }; + + var doc2 = new OpenApiDocument() { Components = new OpenApiComponents() { @@ -66,8 +53,11 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() ["test"] = new JsonSchemaBuilder().Type(SchemaValueType.String).Description("The referenced one").Build() } } - }); - Assert.Equal(2, workspace.Documents.Count()); + }; + + doc.Workspace.RegisterComponents(doc2); + + Assert.Equal(1, doc.Workspace.ComponentsCount()); } [Fact] @@ -77,9 +67,9 @@ public void OpenApiWorkspacesCanResolveExternalReferences() var workspace = new OpenApiWorkspace(); var externalDoc = CreateCommonDocument(); - workspace.AddDocument("common", externalDoc); + workspace.RegisterComponent("https://everything.json/common#/components/schemas/test", externalDoc.Components.Schemas["test"]); - var schema = workspace.ResolveReference("test", ReferenceType.Schema, externalDoc.Components); + var schema = workspace.ResolveReference("https://everything.json/common#/components/schemas/test"); Assert.NotNull(schema); Assert.Equal("The referenced one", schema.GetDescription()); @@ -88,8 +78,6 @@ public void OpenApiWorkspacesCanResolveExternalReferences() [Fact] public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() { - var workspace = new OpenApiWorkspace(); - var doc = new OpenApiDocument(); var reference = "common#/components/schemas/test"; doc.CreatePathItem("/", p => @@ -106,31 +94,14 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() ); }); - var refUri = new Uri("https://registry" + reference.Split('#').LastOrDefault()); - workspace.AddDocument("root", doc); - workspace.AddDocument("common", CreateCommonDocument()); + var doc2 = CreateCommonDocument(); + doc.Workspace.RegisterComponents(doc2); + doc2.Workspace.RegisterComponents(doc); + doc.Workspace.AddDocumentId("common", doc2.BaseUri); var errors = doc.ResolveReferences(); Assert.Empty(errors); - - var schema = doc.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; - //var effectiveSchema = schema.GetEffective(doc); - //Assert.False(effectiveSchema.UnresolvedReference); - } - - [Fact] - public void OpenApiWorkspacesShouldNormalizeDocumentLocations() - { - var workspace = new OpenApiWorkspace(); - workspace.AddDocument("hello", new()); - workspace.AddDocument("hi", new()); - - Assert.True(workspace.Contains("./hello")); - Assert.True(workspace.Contains("./foo/../hello")); - Assert.True(workspace.Contains("file://" + Environment.CurrentDirectory + "/./foo/../hello")); - - Assert.False(workspace.Contains("./goodbye")); } - + // Enable Workspace to load from any reader, not just streams. // Test fragments @@ -145,17 +116,10 @@ public void OpenApiWorkspacesCanResolveReferencesToDocumentFragments() // Arrange var workspace = new OpenApiWorkspace(); var schemaFragment = new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Schema from a fragment").Build(); - workspace.AddSchemaFragment("common", schemaFragment); + workspace.RegisterComponent("common#/components/schemas/test", schemaFragment); // Act - var reference = new OpenApiReference() - { - ExternalResource = "common#/components/schemas/test", - Id = "test", - Type = ReferenceType.Schema - }; - - var schema = workspace.ResolveReference(reference); + var schema = workspace.ResolveReference("common#/components/schemas/test"); // Assert Assert.NotNull(schema); @@ -174,17 +138,14 @@ public void OpenApiWorkspacesCanResolveReferencesToDocumentFragmentsWithJsonPoin { "header1", new OpenApiHeader() } } }; - workspace.AddFragment("fragment", responseFragment); + + workspace.RegisterComponent("headers/header1", responseFragment); // Act - var resolvedElement = workspace.ResolveReference(new() - { - Id = "headers/header1", - ExternalResource = "fragment" - }); + var resolvedElement = workspace.ResolveReference("headers/header1"); // Assert - Assert.Same(responseFragment.Headers["header1"], resolvedElement); + Assert.Same(responseFragment.Headers["header1"], resolvedElement.Headers["header1"]); } // Test artifacts From 6954790d0faead0c984ceb69346b0a0b3da20c07 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 4 Apr 2024 02:49:42 +0300 Subject: [PATCH 28/34] Remove unnecessary code --- src/Microsoft.OpenApi/Models/OpenApiDocument.cs | 8 +------- .../Reader/V3/OpenApiDocumentDeserializer.cs | 3 +-- .../Reader/V31/OpenApiDocumentDeserializer.cs | 3 +-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 6e8a31c27..f81657c63 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; @@ -692,12 +692,6 @@ public JsonSchema FindSubschema(Json.Pointer.JsonPointer pointer, EvaluationOpti { throw new NotImplementedException(); } - - internal JsonSchema ResolveJsonSchemaReference(Uri reference) - { - var referencePath = string.Concat("https://registry", reference.OriginalString.Split('#').Last()); - return (JsonSchema)SchemaRegistry.Global.Get(new Uri(referencePath)); - } } internal class FindSchemaReferences : OpenApiVisitorBase diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs index fe964a3b7..3ed838de9 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.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 Microsoft.OpenApi.Extensions; @@ -50,7 +50,6 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) { var openApiDoc = new OpenApiDocument(); var openApiNode = rootNode.GetMap(); - var openApiDoc = new OpenApiDocument(); ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs index 00a1a3a94..e4de78613 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Reader.ParseNodes; @@ -49,7 +49,6 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) { var openApiDoc = new OpenApiDocument(); var openApiNode = rootNode.GetMap(); - var openApiDoc = new OpenApiDocument(); ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); From f6912abf79814abb7ea96d309edfd3471f2c0117 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 4 Apr 2024 18:50:45 +0300 Subject: [PATCH 29/34] Resolve merge conflicts with release/2.0.0 branch --- .../Models/OpenApiConstants.cs | 2 +- .../Models/OpenApiDocument.cs | 8 +- .../Models/OpenApiExample.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiHeader.cs | 4 +- .../Models/OpenApiParameter.cs | 4 +- .../References/OpenApiExampleReference.cs | 9 +- .../References/OpenApiHeaderReference.cs | 6 +- .../Models/References/OpenApiLinkReference.cs | 4 +- .../References/OpenApiParameterReference.cs | 4 +- .../References/OpenApiPathItemReference.cs | 5 +- .../References/OpenApiRequestBodyReference.cs | 4 +- .../References/OpenApiResponseReference.cs | 4 +- .../OpenApiSecuritySchemeReference.cs | 4 +- .../Models/References/OpenApiTagReference.cs | 4 +- .../Reader/OpenApiJsonReader.cs | 14 + .../Services/JsonSchemaReferenceResolver.cs | 215 +++++++++ .../Services/OpenApiReferenceResolver.cs | 446 ------------------ .../OpenApiDiagnosticTests.cs | 3 +- .../V2Tests/OpenApiDocumentTests.cs | 9 +- .../V31Tests/OpenApiDocumentTests.cs | 25 +- .../V3Tests/JsonSchemaTests.cs | 41 +- .../V3Tests/OpenApiDocumentTests.cs | 262 +++++----- .../V3Tests/OpenApiParameterTests.cs | 3 + .../OpenApiCallbackReferenceTests.cs | 4 +- ...orks_produceTerseOutput=False.verified.txt | 4 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 4 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 2 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 2 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 2 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 2 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...sync_produceTerseOutput=False.verified.txt | 2 +- ...Async_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 3 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 3 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 4 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- .../OpenApiPathItemReferenceTests.cs | 5 +- .../Workspaces/OpenApiWorkspaceTests.cs | 2 +- 46 files changed, 488 insertions(+), 653 deletions(-) create mode 100644 src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs delete mode 100644 src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 3db125b37..3385e03aa 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -628,7 +628,7 @@ public static class OpenApiConstants /// /// The default registry uri for OpenApi documents and workspaces /// - public const string BaseRegistryUri = "http://openapi.net/"; + public const string BaseRegistryUri = "https://openapi.net/"; #region V2.0 diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index f81657c63..17d5abdbd 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -451,12 +451,12 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList /// This method will be replaced by a LoadExternalReferences in the next major update to this library. /// Resolving references at load time is going to go away. /// - public IEnumerable ResolveReferences() + public IEnumerable ResolveJsonSchemaReferences() { - var resolver = new OpenApiReferenceResolver(this, false); - var walker = new OpenApiWalker(resolver); + var jsonSchemaResolver = new JsonSchemaReferenceResolver(this); + var walker = new OpenApiWalker(jsonSchemaResolver); walker.Walk(this); - return resolver.Errors; + return jsonSchemaResolver.Errors; } /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index d55c57daa..467e7b34b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -68,7 +68,7 @@ public OpenApiExample(OpenApiExample example) { Summary = example?.Summary ?? Summary; Description = example?.Description ?? Description; - Value = JsonNodeCloneHelper.Clone(example?.Value); + Value = example?.Value ?? JsonNodeCloneHelper.Clone(example?.Value); ExternalValue = example?.ExternalValue ?? ExternalValue; Extensions = example?.Extensions != null ? new Dictionary(example.Extensions) : null; Reference = example?.Reference != null ? new(example?.Reference) : null; diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index 25d55f002..9655bf587 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -114,8 +114,8 @@ public OpenApiHeader(OpenApiHeader header) Style = header?.Style ?? Style; Explode = header?.Explode ?? Explode; AllowReserved = header?.AllowReserved ?? AllowReserved; - _schema = JsonNodeCloneHelper.CloneJsonSchema(header?.Schema); - Example = JsonNodeCloneHelper.Clone(header?.Example); + Schema = header?.Schema != null ? JsonNodeCloneHelper.CloneJsonSchema(header?.Schema) : null; + Example = header?.Example != null ? JsonNodeCloneHelper.Clone(header?.Example) : null; Examples = header?.Examples != null ? new Dictionary(header.Examples) : null; Content = header?.Content != null ? new Dictionary(header.Content) : null; Extensions = header?.Extensions != null ? new Dictionary(header.Extensions) : null; diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 8c33a4412..399dd8cd9 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -167,9 +167,9 @@ public OpenApiParameter(OpenApiParameter parameter) Style = parameter?.Style ?? Style; Explode = parameter?.Explode ?? Explode; AllowReserved = parameter?.AllowReserved ?? AllowReserved; - _schema = JsonNodeCloneHelper.CloneJsonSchema(parameter?.Schema); + Schema = parameter?.Schema != null ? JsonNodeCloneHelper.CloneJsonSchema(parameter?.Schema) : null; Examples = parameter?.Examples != null ? new Dictionary(parameter.Examples) : null; - Example = JsonNodeCloneHelper.Clone(parameter?.Example); + Example = parameter?.Example != null ? JsonNodeCloneHelper.Clone(parameter?.Example) : null; Content = parameter?.Content != null ? new Dictionary(parameter.Content) : null; Extensions = parameter?.Extensions != null ? new Dictionary(parameter.Extensions) : null; AllowEmptyValue = parameter?.AllowEmptyValue ?? AllowEmptyValue; diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs index bf1de88e1..b177bc059 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs @@ -24,7 +24,10 @@ private OpenApiExample Target get { _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); - return _target; + OpenApiExample resolved = new OpenApiExample(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + if (!string.IsNullOrEmpty(_summary)) resolved.Summary = _summary; + return resolved; } } @@ -71,12 +74,12 @@ internal OpenApiExampleReference(OpenApiExample target, string referenceId) public override string Description { get => string.IsNullOrEmpty(_description) ? Target.Description : _description; - set => _description = value; + set => _description = value; } /// public override string Summary - { + { get => string.IsNullOrEmpty(_summary) ? Target.Summary : _summary; set => _summary = value; } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs index e934e3269..b878898bf 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs @@ -24,7 +24,9 @@ private OpenApiHeader Target get { _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); - return _target; + OpenApiHeader resolved = new OpenApiHeader(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; } } @@ -153,7 +155,7 @@ public override void SerializeAsV2(IOpenApiWriter writer) private void SerializeInternal(IOpenApiWriter writer, Action action) { - Utils.CheckArgumentNull(writer);; + Utils.CheckArgumentNull(writer); action(writer, Target); } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs index 15c48c96e..ffc7f3532 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs @@ -22,7 +22,9 @@ private OpenApiLink Target get { _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); - return _target; + OpenApiLink resolved = new OpenApiLink(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs index 73f126b9e..6722bf1bd 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs @@ -26,7 +26,9 @@ private OpenApiParameter Target get { _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); - return _target; + OpenApiParameter resolved = new OpenApiParameter(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs index ffd241118..21979093c 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs @@ -23,7 +23,10 @@ private OpenApiPathItem Target get { _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); - return _target; + OpenApiPathItem resolved = new OpenApiPathItem(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + if (!string.IsNullOrEmpty(_summary)) resolved.Summary = _summary; + return resolved; } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs index 4dec5c246..be6399c9f 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs @@ -22,7 +22,9 @@ private OpenApiRequestBody Target get { _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); - return _target; + OpenApiRequestBody resolved = new OpenApiRequestBody(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs index 538b7d05d..cf5d06bb5 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs @@ -22,7 +22,9 @@ private OpenApiResponse Target get { _target ??= Reference.HostDocument?.ResolveReferenceTo(_reference); - return _target; + OpenApiResponse resolved = new OpenApiResponse(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs index 21473f9ff..74a6828d7 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs @@ -22,7 +22,9 @@ private OpenApiSecurityScheme Target get { _target ??= Reference.HostDocument.ResolveReferenceTo(_reference); - return _target; + OpenApiSecurityScheme resolved = new OpenApiSecurityScheme(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs index 0d9017de6..7f0bd2a50 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs @@ -22,7 +22,9 @@ private OpenApiTag Target { _target ??= Reference.HostDocument?.ResolveReferenceTo(_reference); _target ??= new OpenApiTag() { Name = _reference.Id }; - return _target; + OpenApiTag resolved = new OpenApiTag(_target); + if (!string.IsNullOrEmpty(_description)) resolved.Description = _description; + return resolved; } } diff --git a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs index bbf928441..0cfcbab24 100644 --- a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs +++ b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs @@ -14,6 +14,8 @@ using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Reader.Services; +using System.Collections.Generic; +using System; namespace Microsoft.OpenApi.Reader { @@ -94,6 +96,7 @@ public async Task ReadAsync(JsonNode jsonNode, } SetHostDocument(document); + ResolveReferences(diagnostic, document); } catch (OpenApiException ex) { @@ -202,5 +205,16 @@ private void SetHostDocument(OpenApiDocument document) { document.SetHostDocument(); } + + private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document) + { + List errors = new(); + errors.AddRange(document.ResolveJsonSchemaReferences()); + + foreach (var item in errors) + { + diagnostic.Errors.Add(item); + } + } } } diff --git a/src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs b/src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs new file mode 100644 index 000000000..845e50556 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Json.Schema; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// This class is used to walk an OpenApiDocument and convert unresolved references to references to populated objects + /// + public class JsonSchemaReferenceResolver : OpenApiVisitorBase + { + private readonly OpenApiDocument _currentDocument; + private readonly List _errors = new(); + + /// + /// Initializes the class. + /// + public JsonSchemaReferenceResolver(OpenApiDocument currentDocument) + { + _currentDocument = currentDocument; + } + + /// + /// List of errors related to the OpenApiDocument + /// + public IEnumerable Errors => _errors; + + /// + /// Visits the referenceable element in the host document + /// + /// The referenceable element in the doc. + public override void Visit(IOpenApiReferenceable referenceable) + { + if (referenceable.Reference != null) + { + referenceable.Reference.HostDocument = _currentDocument; + } + } + + /// + /// Resolves schemas in components + /// + /// + public override void Visit(OpenApiComponents components) + { + components.Schemas = ResolveJsonSchemas(components.Schemas); + } + + /// + /// Resolve all JsonSchema references used in mediaType object + /// + /// + public override void Visit(OpenApiMediaType mediaType) + { + ResolveJsonSchema(mediaType.Schema, r => mediaType.Schema = r ?? mediaType.Schema); + } + + /// + /// Resolve all JsonSchema references used in a parameter + /// + public override void Visit(OpenApiParameter parameter) + { + ResolveJsonSchema(parameter.Schema, r => parameter.Schema = r); + } + + /// + /// Resolve all references used in a JsonSchema + /// + /// + public override void Visit(ref JsonSchema schema) + { + var reference = schema.GetRef(); + var description = schema.GetDescription(); + var summary = schema.GetSummary(); + + if (schema.Keywords.Count.Equals(1) && reference != null) + { + schema = ResolveJsonSchemaReference(reference, description, summary); + } + + var builder = new JsonSchemaBuilder(); + if (schema?.Keywords is { } keywords) + { + foreach (var keyword in keywords) + { + builder.Add(keyword); + } + } + + ResolveJsonSchema(schema.GetItems(), r => builder.Items(r)); + ResolveJsonSchemaList((IList)schema.GetOneOf(), r => builder.OneOf(r)); + ResolveJsonSchemaList((IList)schema.GetAllOf(), r => builder.AllOf(r)); + ResolveJsonSchemaList((IList)schema.GetAnyOf(), r => builder.AnyOf(r)); + ResolveJsonSchemaMap((IDictionary)schema.GetProperties(), r => builder.Properties((IReadOnlyDictionary)r)); + ResolveJsonSchema(schema.GetAdditionalProperties(), r => builder.AdditionalProperties(r)); + + schema = builder.Build(); + } + + /// + /// Visits an IBaseDocument instance + /// + /// + public override void Visit(IBaseDocument document) { } + + private Dictionary ResolveJsonSchemas(IDictionary schemas) + { + var resolvedSchemas = new Dictionary(); + foreach (var schema in schemas) + { + var schemaValue = schema.Value; + Visit(ref schemaValue); + resolvedSchemas[schema.Key] = schemaValue; + } + + return resolvedSchemas; + } + + /// + /// Resolves the target to a JSON schema reference by retrieval from Schema registry + /// + /// The JSON schema reference. + /// The schema's description. + /// The schema's summary. + /// + public JsonSchema ResolveJsonSchemaReference(Uri reference, string description = null, string summary = null) + { + var resolvedSchema = _currentDocument.ResolveJsonSchemaReference(reference); + + if (resolvedSchema != null) + { + var resolvedSchemaBuilder = new JsonSchemaBuilder(); + + foreach (var keyword in resolvedSchema.Keywords) + { + resolvedSchemaBuilder.Add(keyword); + + // Replace the resolved schema's description with that of the schema reference + if (!string.IsNullOrEmpty(description)) + { + resolvedSchemaBuilder.Description(description); + } + + // Replace the resolved schema's summary with that of the schema reference + if (!string.IsNullOrEmpty(summary)) + { + resolvedSchemaBuilder.Summary(summary); + } + } + + return resolvedSchemaBuilder.Build(); + } + else + { + var referenceId = reference.OriginalString.Split('/').LastOrDefault(); + throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); + } + } + + private void ResolveJsonSchema(JsonSchema schema, Action assign) + { + if (schema == null) return; + var reference = schema.GetRef(); + var description = schema.GetDescription(); + var summary = schema.GetSummary(); + + if (reference != null) + { + assign(ResolveJsonSchemaReference(reference, description, summary)); + } + } + + private void ResolveJsonSchemaList(IList list, Action> assign) + { + if (list == null) return; + + for (int i = 0; i < list.Count; i++) + { + var entity = list[i]; + var reference = entity?.GetRef(); + if (reference != null) + { + list[i] = ResolveJsonSchemaReference(reference); + } + } + + assign(list.ToList()); + } + + private void ResolveJsonSchemaMap(IDictionary map, Action> assign) + { + if (map == null) return; + + foreach (var key in map.Keys.ToList()) + { + var entity = map[key]; + var reference = entity.GetRef(); + if (reference != null) + { + map[key] = ResolveJsonSchemaReference(reference); + } + } + + assign(map.ToDictionary(e => e.Key, e => e.Value)); + } + } +} diff --git a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs deleted file mode 100644 index 4c89d7796..000000000 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using Json.Schema; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Services -{ - /// - /// This class is used to walk an OpenApiDocument and convert unresolved references to references to populated objects - /// - public class OpenApiReferenceResolver : OpenApiVisitorBase - { - private OpenApiDocument _currentDocument; - private readonly bool _resolveRemoteReferences; - private List _errors = new(); - - /// - /// Initializes the class. - /// - public OpenApiReferenceResolver(OpenApiDocument currentDocument, bool resolveRemoteReferences = true) - { - _currentDocument = currentDocument; - _resolveRemoteReferences = resolveRemoteReferences; - } - - /// - /// List of errors related to the OpenApiDocument - /// - public IEnumerable Errors => _errors; - - /// - /// Resolves tags in OpenApiDocument - /// - /// - public override void Visit(OpenApiDocument doc) - { - if (doc.Tags != null) - { - ResolveTags(doc.Tags); - } - } - - /// - /// Visits the referenceable element in the host document - /// - /// The referenceable element in the doc. - public override void Visit(IOpenApiReferenceable referenceable) - { - if (referenceable.Reference != null) - { - referenceable.Reference.HostDocument = _currentDocument; - } - } - - /// - /// Resolves references in components - /// - /// - public override void Visit(OpenApiComponents components) - { - ResolveMap(components.Parameters); - ResolveMap(components.RequestBodies); - ResolveMap(components.Responses); - ResolveMap(components.Links); - ResolveMap(components.Callbacks); - ResolveMap(components.Examples); - components.Schemas = ResolveJsonSchemas(components.Schemas); - ResolveMap(components.PathItems); - ResolveMap(components.SecuritySchemes); - ResolveMap(components.Headers); - } - - /// - /// Resolves all references used in callbacks - /// - /// - public override void Visit(IDictionary callbacks) - { - ResolveMap(callbacks); - } - - /// - /// Resolves all references used in webhooks - /// - /// - public override void Visit(IDictionary webhooks) - { - ResolveMap(webhooks); - } - - /// - /// Resolve all references used in an operation - /// - public override void Visit(OpenApiOperation operation) - { - ResolveObject(operation.RequestBody, r => operation.RequestBody = r); - ResolveList(operation.Parameters); - - if (operation.Tags != null) - { - ResolveTags(operation.Tags); - } - } - - /// - /// Resolve all references used in mediaType object - /// - /// - public override void Visit(OpenApiMediaType mediaType) - { - ResolveJsonSchema(mediaType.Schema, r => mediaType.Schema = r ?? mediaType.Schema); - } - - /// - /// Resolve all references to examples - /// - /// - public override void Visit(IDictionary examples) - { - ResolveMap(examples); - } - - /// - /// Resolve all references to responses - /// - public override void Visit(OpenApiResponses responses) - { - ResolveMap(responses); - } - - /// - /// Resolve all references to headers - /// - /// - public override void Visit(IDictionary headers) - { - ResolveMap(headers); - } - - /// - /// Resolve all references to SecuritySchemes - /// - public override void Visit(OpenApiSecurityRequirement securityRequirement) - { - foreach (var scheme in securityRequirement.Keys.ToList()) - { - ResolveObject(scheme, (resolvedScheme) => - { - if (resolvedScheme != null) - { - // If scheme was unresolved - // copy Scopes and remove old unresolved scheme - var scopes = securityRequirement[scheme]; - securityRequirement.Remove(scheme); - securityRequirement.Add(resolvedScheme, scopes); - } - }); - } - } - - /// - /// Resolve all references to parameters - /// - public override void Visit(IList parameters) - { - ResolveList(parameters); - } - - /// - /// Resolve all references used in a parameter - /// - public override void Visit(OpenApiParameter parameter) - { - ResolveJsonSchema(parameter.Schema, r => parameter.Schema = r); - ResolveMap(parameter.Examples); - } - - /// - /// Resolve all references to links - /// - public override void Visit(IDictionary links) - { - ResolveMap(links); - } - - /// - /// Resolve all references used in a schem - /// - /// - public override void Visit(ref JsonSchema schema) - { - var reference = schema.GetRef(); - var description = schema.GetDescription(); - var summary = schema.GetSummary(); - - if (schema.Keywords.Count.Equals(1) && reference != null) - { - schema = ResolveJsonSchemaReference(reference, description, summary); - } - - var builder = new JsonSchemaBuilder(); - if (schema?.Keywords is { } keywords) - { - foreach (var keyword in keywords) - { - builder.Add(keyword); - } - } - - ResolveJsonSchema(schema.GetItems(), r => builder.Items(r)); - ResolveJsonSchemaList((IList)schema.GetOneOf(), r => builder.OneOf(r)); - ResolveJsonSchemaList((IList)schema.GetAllOf(), r => builder.AllOf(r)); - ResolveJsonSchemaList((IList)schema.GetAnyOf(), r => builder.AnyOf(r)); - ResolveJsonSchemaMap((IDictionary)schema.GetProperties(), r => builder.Properties((IReadOnlyDictionary)r)); - ResolveJsonSchema(schema.GetAdditionalProperties(), r => builder.AdditionalProperties(r)); - - schema = builder.Build(); - } - - /// - /// Visits an IBaseDocument instance - /// - /// - public override void Visit(IBaseDocument document) { } - - private Dictionary ResolveJsonSchemas(IDictionary schemas) - { - var resolvedSchemas = new Dictionary(); - foreach (var schema in schemas) - { - var schemaValue = schema.Value; - Visit(ref schemaValue); - resolvedSchemas[schema.Key] = schemaValue; - } - - return resolvedSchemas; - } - - /// - /// Resolves the target to a JSON schema reference by retrieval from Schema registry - /// - /// The JSON schema reference. - /// The schema's description. - /// The schema's summary. - /// - public JsonSchema ResolveJsonSchemaReference(Uri reference, string description = null, string summary = null) - { - var resolvedSchema = _currentDocument.ResolveJsonSchemaReference(reference); - - if (resolvedSchema != null) - { - var resolvedSchemaBuilder = new JsonSchemaBuilder(); - - foreach (var keyword in resolvedSchema.Keywords) - { - resolvedSchemaBuilder.Add(keyword); - - // Replace the resolved schema's description with that of the schema reference - if (!string.IsNullOrEmpty(description)) - { - resolvedSchemaBuilder.Description(description); - } - - // Replace the resolved schema's summary with that of the schema reference - if (!string.IsNullOrEmpty(summary)) - { - resolvedSchemaBuilder.Summary(summary); - } - } - - return resolvedSchemaBuilder.Build(); - } - else - { - var referenceId = reference.OriginalString.Split('/').LastOrDefault(); - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); - } - } - - /// - /// Replace references to tags with either tag objects declared in components, or inline tag object - /// - private void ResolveTags(IList tags) - { - for (var i = 0; i < tags.Count; i++) - { - var tag = tags[i]; - if (IsUnresolvedReference(tag)) - { - var resolvedTag = ResolveReference(tag.Reference); - - if (resolvedTag == null) - { - resolvedTag = new() - { - Name = tag.Reference.Id - }; - } - tags[i] = resolvedTag; - } - } - } - - private void ResolveObject(T entity, Action assign) where T : class, IOpenApiReferenceable, new() - { - if (entity == null) return; - - if (IsUnresolvedReference(entity)) - { - assign(ResolveReference(entity.Reference)); - } - } - - private void ResolveJsonSchema(JsonSchema schema, Action assign) - { - if (schema == null) return; - var reference = schema.GetRef(); - var description = schema.GetDescription(); - var summary = schema.GetSummary(); - - if (reference != null) - { - assign(ResolveJsonSchemaReference(reference, description, summary)); - } - } - - private void ResolveList(IList list) where T : class, IOpenApiReferenceable, new() - { - if (list == null) return; - - for (var i = 0; i < list.Count; i++) - { - var entity = list[i]; - if (IsUnresolvedReference(entity)) - { - list[i] = ResolveReference(entity.Reference); - } - } - } - - private void ResolveJsonSchemaList(IList list, Action> assign) - { - if (list == null) return; - - for (int i = 0; i < list.Count; i++) - { - var entity = list[i]; - var reference = entity?.GetRef(); - if (reference != null) - { - list[i] = ResolveJsonSchemaReference(reference); - } - } - - assign(list.ToList()); - } - - private void ResolveMap(IDictionary map) where T : class, IOpenApiReferenceable, new() - { - if (map == null) return; - - foreach (var key in map.Keys.ToList()) - { - var entity = map[key]; - if (IsUnresolvedReference(entity)) - { - map[key] = ResolveReference(entity.Reference); - } - } - } - - private void ResolveJsonSchemaMap(IDictionary map, Action> assign) - { - if (map == null) return; - - foreach (var key in map.Keys.ToList()) - { - var entity = map[key]; - var reference = entity.GetRef(); - if (reference != null) - { - map[key] = ResolveJsonSchemaReference(reference); - } - } - - assign(map.ToDictionary(e => e.Key, e => e.Value)); - } - - private T ResolveReference(OpenApiReference reference) where T : class, IOpenApiReferenceable, new() - { - if (string.IsNullOrEmpty(reference?.ExternalResource)) - { - try - { - return _currentDocument.ResolveReference(reference, false) as T; - } - catch (OpenApiException ex) - { - _errors.Add(new OpenApiReferenceError(ex)); - return null; - } - } - // The concept of merging references with their target at load time is going away in the next major version - // External references will not support this approach. - //else if (_resolveRemoteReferences == true) - //{ - // if (_currentDocument.Workspace == null) - // { - // _errors.Add(new OpenApiReferenceError(reference,"Cannot resolve external references for documents not in workspaces.")); - // // Leave as unresolved reference - // return new T() - // { - // UnresolvedReference = true, - // Reference = reference - // }; - // } - // var target = _currentDocument.Workspace.ResolveReference(reference); - - // // TODO: If it is a document fragment, then we should resolve it within the current context - - // return target as T; - //} - else - { - // Leave as unresolved reference - return new() - { - UnresolvedReference = true, - Reference = reference - }; - } - } - - private bool IsUnresolvedReference(IOpenApiReferenceable possibleReference) - { - return possibleReference != null && possibleReference.UnresolvedReference; - } - } -} diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index ba2f37249..9ec7afb3a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -57,9 +57,10 @@ public async Task DiagnosticReportMergedForExternalReference() Assert.NotNull(result); Assert.NotNull(result.OpenApiDocument.Workspace); - result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List + result.OpenApiDiagnostic.Errors.Should().BeEquivalentTo(new List { new OpenApiError("", "[File: ./TodoReference.yaml] Paths is a REQUIRED field at #/"), + new(new OpenApiException("[File: ./TodoReference.yaml] Invalid Reference identifier 'object-not-existing'.")) }); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index e60a38df2..611f2c3d5 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.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; @@ -42,12 +42,12 @@ public void ShouldParseProducesInAnyOrder() var okMediaType = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(new JsonSchemaBuilder().Ref("#/definitions/Item")) + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(okSchema) }; var errorMediaType = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/definitions/Error") + Schema = errorSchema }; result.OpenApiDocument.Should().BeEquivalentTo(new OpenApiDocument @@ -169,6 +169,7 @@ public void ShouldAssignSchemaToAllResponses() .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier.")))); var errorSchema = new JsonSchemaBuilder() + .Ref("#/definitions/Error") .Properties(("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Format("int32")), ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), ("fields", new JsonSchemaBuilder().Type(SchemaValueType.String))); @@ -182,8 +183,6 @@ public void ShouldAssignSchemaToAllResponses() Assert.NotNull(json); Assert.Equal(json.Schema.Keywords.Count, targetSchema.Keywords.Count); - Assert.Equal(json.Schema.Keywords.Count, targetSchema.Keywords.Count); - var xml = response.Value.Content["application/xml"]; Assert.NotNull(xml); Assert.Equal(xml.Schema.Keywords.Count, targetSchema.Keywords.Count); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index 07c7d9ec4..087220fa7 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.IO; using FluentAssertions; @@ -133,14 +133,14 @@ public void ParseDocumentWithWebhooksShouldSucceed() { Schema = new JsonSchemaBuilder() .Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Ref("#/components/schemas/petSchema")) + .Items(petSchema) }, ["application/xml"] = new OpenApiMediaType { Schema = new JsonSchemaBuilder() .Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Ref("#/components/schemas/petSchema")) + .Items(petSchema) } } } @@ -156,7 +156,7 @@ public void ParseDocumentWithWebhooksShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/newPetSchema") + Schema = newPetSchema } } }, @@ -169,7 +169,7 @@ public void ParseDocumentWithWebhooksShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/petSchema") + Schema = petSchema } } } @@ -181,8 +181,7 @@ public void ParseDocumentWithWebhooksShouldSucceed() Components = components }; - // Assert - var schema = actual.OpenApiDocument.Webhooks["pets"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; + // Assert actual.OpenApiDiagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); } @@ -261,13 +260,13 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() { Schema = new JsonSchemaBuilder() .Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Ref("#/components/schemas/petSchema")) + .Items(petSchema) }, ["application/xml"] = new OpenApiMediaType { Schema = new JsonSchemaBuilder() .Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Ref("#/components/schemas/petSchema")) + .Items(petSchema) } } } @@ -283,7 +282,7 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/newPetSchema") + Schema = newPetSchema } } }, @@ -296,7 +295,7 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/petSchema") + Schema = petSchema }, } } @@ -322,7 +321,9 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() }; // Assert - actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace) + actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options + .Excluding(x => x.Webhooks["pets"].Reference) + .Excluding(x => x.Workspace) .Excluding(y => y.BaseUri)); actual.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 }); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs index 25871e25e..50cadb81c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.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.Collections.Generic; @@ -220,7 +220,7 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, Errors = new List() { - new OpenApiError("", "Paths is a REQUIRED field at #/") + new OpenApiError("", "Paths is a REQUIRED field at #/") } }); @@ -228,22 +228,27 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() { Schemas = { - ["ErrorModel"] = new JsonSchemaBuilder() - .Ref("#/components/schemas/ErrorModel") - .Type(SchemaValueType.Object) - .Required("message", "code") - .Properties( - ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), - ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Minimum(100).Maximum(600))), - ["ExtendedErrorModel"] = new JsonSchemaBuilder() - .Ref("#/components/schemas/ExtendedErrorModel") - .AllOf( - new JsonSchemaBuilder() - .Ref("#/components/schemas/ErrorModel"), - new JsonSchemaBuilder() - .Type(SchemaValueType.Object) - .Required("rootCause") - .Properties(("rootCause", new JsonSchemaBuilder().Type(SchemaValueType.String)))) + ["ErrorModel"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/ErrorModel") + .Type(SchemaValueType.Object) + .Required("message", "code") + .Properties( + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Minimum(100).Maximum(600))), + ["ExtendedErrorModel"] = new JsonSchemaBuilder() + .Ref("#/components/schemas/ExtendedErrorModel") + .AllOf( + new JsonSchemaBuilder() + .Ref("#/components/schemas/ErrorModel") + .Type(SchemaValueType.Object) + .Properties( + ("code", new JsonSchemaBuilder().Type(SchemaValueType.Integer).Minimum(100).Maximum(600)), + ("message", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Required("message", "code"), + new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("rootCause") + .Properties(("rootCause", new JsonSchemaBuilder().Type(SchemaValueType.String)))) } }; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 9b432185f..ba569a415 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -100,7 +100,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() - { + { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, Errors = new List() { @@ -117,7 +117,7 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() - { + { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, Errors = new List() { @@ -207,7 +207,8 @@ public void ParseMinimalDocumentShouldSucceed() [Fact] public void ParseStandardPetStoreDocumentShouldSucceed() { - var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "petStore.yaml")); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml")); + var result = OpenApiDocument.Load(stream, OpenApiConstants.Yaml); var components = new OpenApiComponents { @@ -238,6 +239,11 @@ public void ParseStandardPetStoreDocumentShouldSucceed() ("message", new JsonSchemaBuilder().Type(SchemaValueType.String))) } }; + var petSchema = components.Schemas["pet1"]; + + var newPetSchema = components.Schemas["newPet"]; + + var errorModelSchema = components.Schemas["errorModel"]; var expectedDoc = new OpenApiDocument { @@ -307,13 +313,11 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Ref("#/components/schemas/pet1")) + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(petSchema) }, ["application/xml"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Ref("#/components/schemas/pet1")) + Schema = new JsonSchemaBuilder().Type(SchemaValueType.Array).Items(petSchema) } } }, @@ -324,7 +328,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } }, @@ -335,7 +339,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } } @@ -353,7 +357,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/newPet") + Schema = newPetSchema } } }, @@ -366,7 +370,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/pet1") + Schema = petSchema }, } }, @@ -377,7 +381,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } }, @@ -388,7 +392,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } } @@ -425,11 +429,11 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/pet1") + Schema = petSchema }, ["application/xml"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/pet1") + Schema = petSchema } } }, @@ -440,7 +444,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } }, @@ -451,7 +455,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } } @@ -485,7 +489,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } }, @@ -496,7 +500,7 @@ public void ParseStandardPetStoreDocumentShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } } @@ -510,14 +514,15 @@ public void ParseStandardPetStoreDocumentShouldSucceed() result.OpenApiDocument.Should().BeEquivalentTo(expectedDoc, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - result.OpenApiDiagnostic.Should().BeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + result.OpenApiDiagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } [Fact] public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { - var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "petStoreWithTagAndSecurity.yaml")); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStoreWithTagAndSecurity.yaml")); + var actual = OpenApiDocument.Load(stream, OpenApiConstants.Yaml); var components = new OpenApiComponents { @@ -563,6 +568,12 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; + var petSchema = components.Schemas["pet1"]; + + var newPetSchema = components.Schemas["newPet"]; + + var errorModelSchema = components.Schemas["errorModel"]; + var tag1 = new OpenApiTag { Name = "tagName1", @@ -574,6 +585,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; + var tag2 = new OpenApiTag { Name = "tagName2", @@ -622,12 +634,12 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }, Servers = new List - { - new OpenApiServer { - Url = "http://petstore.swagger.io/api" - } - }, + new OpenApiServer + { + Url = "http://petstore.swagger.io/api" + } + }, Paths = new OpenApiPaths { ["/pets"] = new OpenApiPathItem @@ -637,35 +649,35 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() [OperationType.Get] = new OpenApiOperation { Tags = new List - { - tag1, - tag2 - }, + { + tag1, + tag2 + }, Description = "Returns all pets from the system that the user has access to", OperationId = "findPets", Parameters = new List - { - new OpenApiParameter { - Name = "tags", - In = ParameterLocation.Query, - Description = "tags to filter by", - Required = false, - Schema = new JsonSchemaBuilder() - .Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Type(SchemaValueType.String)) + new OpenApiParameter + { + Name = "tags", + In = ParameterLocation.Query, + Description = "tags to filter by", + Required = false, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Array) + .Items(new JsonSchemaBuilder().Type(SchemaValueType.String)) + }, + new OpenApiParameter + { + Name = "limit", + In = ParameterLocation.Query, + Description = "maximum number of results to return", + Required = false, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int32") + } }, - new OpenApiParameter - { - Name = "limit", - In = ParameterLocation.Query, - Description = "maximum number of results to return", - Required = false, - Schema = new JsonSchemaBuilder() - .Type(SchemaValueType.Integer) - .Format("int32") - } - }, Responses = new OpenApiResponses { ["200"] = new OpenApiResponse @@ -677,13 +689,13 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { Schema = new JsonSchemaBuilder() .Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Ref("#/components/schemas/pet1")) + .Items(petSchema) }, ["application/xml"] = new OpenApiMediaType { Schema = new JsonSchemaBuilder() .Type(SchemaValueType.Array) - .Items(new JsonSchemaBuilder().Ref("#/components/schemas/pet1")) + .Items(petSchema) } } }, @@ -694,7 +706,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } }, @@ -705,7 +717,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } } @@ -714,10 +726,10 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() [OperationType.Post] = new OpenApiOperation { Tags = new List - { - tag1, - tag2 - }, + { + tag1, + tag2 + }, Description = "Creates a new pet in the store. Duplicates are allowed", OperationId = "addPet", RequestBody = new OpenApiRequestBody @@ -728,7 +740,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/newPet") + Schema = newPetSchema } } }, @@ -741,7 +753,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/pet1") + Schema = petSchema }, } }, @@ -752,7 +764,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } }, @@ -763,23 +775,23 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } } }, Security = new List - { - new OpenApiSecurityRequirement { - [securityScheme1] = new List(), - [securityScheme2] = new List + new OpenApiSecurityRequirement { - "scope1", - "scope2" + [securityScheme1] = new List(), + [securityScheme2] = new List + { + "scope1", + "scope2" + } } } - } } } }, @@ -793,18 +805,18 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() "Returns a user based on a single ID, if the user does not have access to the pet", OperationId = "findPetById", Parameters = new List - { - new OpenApiParameter { - Name = "id", - In = ParameterLocation.Path, - Description = "ID of pet to fetch", - Required = true, - Schema = new JsonSchemaBuilder() - .Type(SchemaValueType.Integer) - .Format("int64") - } - }, + new OpenApiParameter + { + Name = "id", + In = ParameterLocation.Path, + Description = "ID of pet to fetch", + Required = true, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64") + } + }, Responses = new OpenApiResponses { ["200"] = new OpenApiResponse @@ -814,11 +826,11 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["application/json"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/pet1") + Schema = petSchema }, ["application/xml"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/pet1") + Schema = petSchema } } }, @@ -829,7 +841,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } }, @@ -840,7 +852,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } } @@ -851,18 +863,18 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() Description = "deletes a single pet based on the ID supplied", OperationId = "deletePet", Parameters = new List - { - new OpenApiParameter { - Name = "id", - In = ParameterLocation.Path, - Description = "ID of pet to delete", - Required = true, - Schema = new JsonSchemaBuilder() - .Type(SchemaValueType.Integer) - .Format("int64") - } - }, + new OpenApiParameter + { + Name = "id", + In = ParameterLocation.Path, + Description = "ID of pet to delete", + Required = true, + Schema = new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64") + } + }, Responses = new OpenApiResponses { ["204"] = new OpenApiResponse @@ -876,7 +888,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } }, @@ -887,7 +899,7 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { ["text/html"] = new OpenApiMediaType { - Schema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel") + Schema = errorModelSchema } } } @@ -898,26 +910,26 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() }, Components = components, Tags = new List - { - new OpenApiTag { - Name = "tagName1", - Description = "tagDescription1" - } - }, + new OpenApiTag + { + Name = "tagName1", + Description = "tagDescription1" + } + }, SecurityRequirements = new List - { - new OpenApiSecurityRequirement { - [securityScheme1] = new List(), - [securityScheme2] = new List + new OpenApiSecurityRequirement { - "scope1", - "scope2", - "scope3" + [securityScheme1] = new List(), + [securityScheme2] = new List + { + "scope1", + "scope2", + "scope3" + } } } - } }; actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options @@ -928,12 +940,13 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() .Excluding(x => x.Paths["/pets"].Operations[OperationType.Post].Tags[0].Reference.HostDocument) .Excluding(x => x.Paths["/pets"].Operations[OperationType.Get].Tags[1].Reference.HostDocument) .Excluding(x => x.Paths["/pets"].Operations[OperationType.Post].Tags[1].Reference.HostDocument) - .Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - + .Excluding(x => x.Workspace) + .Excluding(y => y.BaseUri)); actual.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } + [Fact] public void ParsePetStoreExpandedShouldSucceed() { @@ -1047,10 +1060,16 @@ public void ParseDocumentWithJsonSchemaReferencesWorks() var actualSchema = result.OpenApiDocument.Paths["/users/{userId}"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema; var expectedSchema = new JsonSchemaBuilder() - .Ref("#/components/schemas/User"); + .Ref("#/components/schemas/User") + .Type(SchemaValueType.Object) + .Properties( + ("id", new JsonSchemaBuilder().Type(SchemaValueType.Integer)), + ("username", new JsonSchemaBuilder().Type(SchemaValueType.String)), + ("email", new JsonSchemaBuilder().Type(SchemaValueType.String))) + .Build(); // Assert - Assert.Equal(expectedSchema, actualSchema); + actualSchema.Should().BeEquivalentTo(expectedSchema); } [Fact] @@ -1086,9 +1105,9 @@ public void ParseDocWithRefsUsingProxyReferencesSucceeds() .Format("int32") .Default(10), Reference = new OpenApiReference - { - Id = "LimitParameter", - Type = ReferenceType.Parameter + { + Id = "LimitParameter", + Type = ReferenceType.Parameter } } ], @@ -1113,7 +1132,7 @@ public void ParseDocWithRefsUsingProxyReferencesSucceeds() .Default(10) } } - } + } }; var expectedSerializedDoc = @"openapi: 3.0.1 @@ -1150,6 +1169,7 @@ public void ParseDocWithRefsUsingProxyReferencesSucceeds() // Assert actualParam.Should().BeEquivalentTo(expectedParam, options => options.Excluding(x => x.Reference.HostDocument)); outputDoc.Should().BeEquivalentTo(expectedSerializedDoc.MakeLineBreaksEnvironmentNeutral()); + } - } + } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs index ee3dfe97f..5a6e9fd41 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs @@ -11,6 +11,7 @@ using Microsoft.OpenApi.Reader; using Xunit; using Microsoft.OpenApi.Reader.V3; +using Microsoft.OpenApi.Services; namespace Microsoft.OpenApi.Readers.Tests.V3Tests { @@ -325,6 +326,8 @@ public void ParseParameterWithReferenceWorks() } }; + document.Workspace.RegisterComponents(document); + using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithRef.yaml")); var node = TestHelper.CreateYamlMapNode(stream); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs index f0b89c953..1aa732809 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs @@ -167,7 +167,7 @@ public async Task SerializeCallbackReferenceAsV3JsonWorks(bool produceTerseOutpu { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineExternalReferences = true }); // Act _externalCallbackReference.SerializeAsV3(writer); @@ -184,7 +184,7 @@ public async Task SerializeCallbackReferenceAsV31JsonWorks(bool produceTerseOutp { // Arrange var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineLocalReferences = true }); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput, InlineExternalReferences = true }); // Act _externalCallbackReference.SerializeAsV31(writer); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index 8d9c12611..d3d85c6b5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -1,6 +1,6 @@ { - "summary": "Example of a user", - "description": "This is is an example of a user", + "summary": "Example of a local user", + "description": "This is an example of a local user", "value": [ { "id": 1, diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index c1549bf7c..0c1962929 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"summary":"Example of a user","description":"This is is an example of a user","value":[{"id":1,"name":"John Doe"}]} \ No newline at end of file +{"summary":"Example of a local user","description":"This is an example of a local user","value":[{"id":1,"name":"John Doe"}]} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt index 8d9c12611..d3d85c6b5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -1,6 +1,6 @@ { - "summary": "Example of a user", - "description": "This is is an example of a user", + "summary": "Example of a local user", + "description": "This is an example of a local user", "value": [ { "id": 1, diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt index c1549bf7c..0c1962929 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.SerializeExampleReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"summary":"Example of a user","description":"This is is an example of a user","value":[{"id":1,"name":"John Doe"}]} \ No newline at end of file +{"summary":"Example of a local user","description":"This is an example of a local user","value":[{"id":1,"name":"John Doe"}]} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index f43e25a40..badfda7f7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -1,5 +1,5 @@ { - "description": "The URL of the newly created post", + "description": "Location of the locally referenced post", "schema": { "type": "string" } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index 1b29be17d..cf7cf9e25 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"description":"The URL of the newly created post","schema":{"type":"string"}} \ No newline at end of file +{"description":"Location of the locally referenced post","schema":{"type":"string"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt index f43e25a40..badfda7f7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -1,5 +1,5 @@ { - "description": "The URL of the newly created post", + "description": "Location of the locally referenced post", "schema": { "type": "string" } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt index 1b29be17d..cf7cf9e25 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"description":"The URL of the newly created post","schema":{"type":"string"}} \ No newline at end of file +{"description":"Location of the locally referenced post","schema":{"type":"string"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index 6fe727ea0..89319843f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -3,5 +3,5 @@ "parameters": { "userId": "$response.body#/id" }, - "description": "The id value returned in the response can be used as the userId parameter in GET /users/{userId}" + "description": "Use the id returned as the userId in `GET /users/{userId}`" } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index e3df412e9..93208a391 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"operationId":"getUser","parameters":{"userId":"$response.body#/id"},"description":"The id value returned in the response can be used as the userId parameter in GET /users/{userId}"} \ No newline at end of file +{"operationId":"getUser","parameters":{"userId":"$response.body#/id"},"description":"Use the id returned as the userId in `GET /users/{userId}`"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt index 6fe727ea0..89319843f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -3,5 +3,5 @@ "parameters": { "userId": "$response.body#/id" }, - "description": "The id value returned in the response can be used as the userId parameter in GET /users/{userId}" + "description": "Use the id returned as the userId in `GET /users/{userId}`" } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt index e3df412e9..93208a391 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.SerializeLinkReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"operationId":"getUser","parameters":{"userId":"$response.body#/id"},"description":"The id value returned in the response can be used as the userId parameter in GET /users/{userId}"} \ No newline at end of file +{"operationId":"getUser","parameters":{"userId":"$response.body#/id"},"description":"Use the id returned as the userId in `GET /users/{userId}`"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index 992c2f047..2a64ba6d9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt @@ -1,7 +1,7 @@ { "in": "query", "name": "limit", - "description": "Number of results to return", + "description": "Results to return", "type": "integer", "maximum": 100, "minimum": 1 diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt index 995eb077e..8d3cb1803 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV2JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"in":"query","name":"limit","description":"Number of results to return","type":"integer","maximum":100,"minimum":1} \ No newline at end of file +{"in":"query","name":"limit","description":"Results to return","type":"integer","maximum":100,"minimum":1} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index f0066344e..237298009 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -1,7 +1,8 @@ { "name": "limit", "in": "query", - "description": "Number of results to return", + "description": "Results to return", + "style": "form", "schema": { "maximum": 100, "minimum": 1, diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index 2b7ff1cfb..e8eac1b64 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"name":"limit","in":"query","description":"Number of results to return","schema":{"maximum":100,"minimum":1,"type":"integer"}} \ No newline at end of file +{"name":"limit","in":"query","description":"Results to return","style":"form","schema":{"maximum":100,"minimum":1,"type":"integer"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt index f0066344e..237298009 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -1,7 +1,8 @@ { "name": "limit", "in": "query", - "description": "Number of results to return", + "description": "Results to return", + "style": "form", "schema": { "maximum": 100, "minimum": 1, diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt index 2b7ff1cfb..e8eac1b64 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.SerializeParameterReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"name":"limit","in":"query","description":"Number of results to return","schema":{"maximum":100,"minimum":1,"type":"integer"}} \ No newline at end of file +{"name":"limit","in":"query","description":"Results to return","style":"form","schema":{"maximum":100,"minimum":1,"type":"integer"}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index 844f5ee81..4aa3a9451 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -1,6 +1,6 @@ { - "summary": "User path item summary", - "description": "User path item description", + "summary": "Local reference: User path item summary", + "description": "Local reference: User path item description", "get": { "summary": "Get users", "responses": { diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index f43044ef8..1b04eaa44 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.SerializePathItemReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"summary":"User path item summary","description":"User path item description","get":{"summary":"Get users","responses":{"200":{"description":"Successful operation"}}},"post":{"summary":"Create a user","responses":{"201":{"description":"User created successfully"}}},"delete":{"summary":"Delete a user","responses":{"204":{"description":"User deleted successfully"}}}} \ No newline at end of file +{"summary":"Local reference: User path item summary","description":"Local reference: User path item description","get":{"summary":"Get users","responses":{"200":{"description":"Successful operation"}}},"post":{"summary":"Create a user","responses":{"201":{"description":"User created successfully"}}},"delete":{"summary":"Delete a user","responses":{"204":{"description":"User deleted successfully"}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs index fe40ddc29..84c7bb2a5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.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.Globalization; @@ -42,7 +42,7 @@ public class OpenApiPathItemReferenceTests "; private const string OpenApi_2 = @" -openapi: 3.0.0 +openapi: 3.1.0 info: title: Sample API version: 1.0.0 @@ -85,6 +85,7 @@ public OpenApiPathItemReferenceTests() _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); _localPathItemReference = new OpenApiPathItemReference("userPathItem", _openApiDoc_2) { diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index 68cb9057a..ba99aff49 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -98,7 +98,7 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() doc.Workspace.RegisterComponents(doc2); doc2.Workspace.RegisterComponents(doc); doc.Workspace.AddDocumentId("common", doc2.BaseUri); - var errors = doc.ResolveReferences(); + var errors = doc.ResolveJsonSchemaReferences(); Assert.Empty(errors); } From 33236ad4ad0aa82f0540af59bf520ef15c2ce2fd Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 4 Apr 2024 19:04:51 +0300 Subject: [PATCH 30/34] Merge method resolving JsonSchemas with SetHostDocument --- .../Models/OpenApiDocument.cs | 24 +- .../Reader/OpenApiJsonReader.cs | 8 +- .../Services/HostDocumentResolver.cs | 30 --- .../Services/ReferenceResolver.cs | 209 ++++++++++++++++++ .../Workspaces/OpenApiWorkspaceTests.cs | 2 +- 5 files changed, 217 insertions(+), 56 deletions(-) delete mode 100644 src/Microsoft.OpenApi/Services/HostDocumentResolver.cs create mode 100644 src/Microsoft.OpenApi/Services/ReferenceResolver.cs diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 17d5abdbd..bed24b3c2 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -445,28 +445,15 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList } /// - /// Walk the OpenApiDocument and resolve unresolved references + /// Walks the OpenApiDocument and sets the host document for all IOpenApiReferenceable objects + /// and resolves JsonSchema references /// - /// - /// This method will be replaced by a LoadExternalReferences in the next major update to this library. - /// Resolving references at load time is going to go away. - /// - public IEnumerable ResolveJsonSchemaReferences() + public IEnumerable ResolveReferences() { - var jsonSchemaResolver = new JsonSchemaReferenceResolver(this); - var walker = new OpenApiWalker(jsonSchemaResolver); - walker.Walk(this); - return jsonSchemaResolver.Errors; - } - - /// - /// Walks the OpenApiDocument and sets the host document for all referenceable objects - /// - public void SetHostDocument() - { - var resolver = new HostDocumentResolver(this); + var resolver = new ReferenceResolver(this); var walker = new OpenApiWalker(resolver); walker.Walk(this); + return resolver.Errors; } /// @@ -502,6 +489,7 @@ public JsonSchema ResolveJsonSchemaReference(Uri referenceUri) string uriLocation; string id = referenceUri.OriginalString.Split('/')?.Last(); string relativePath = "/components/" + ReferenceType.Schema.GetDisplayName() + "/" + id; + if (referenceUri.OriginalString.StartsWith("#")) { // Local reference diff --git a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs index 0cfcbab24..07fd6bfff 100644 --- a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs +++ b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs @@ -95,7 +95,6 @@ public async Task ReadAsync(JsonNode jsonNode, } } - SetHostDocument(document); ResolveReferences(diagnostic, document); } catch (OpenApiException ex) @@ -201,15 +200,10 @@ private async Task LoadExternalRefs(OpenApiDocument document, return await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document, format ?? OpenApiConstants.Json, null, cancellationToken); } - private void SetHostDocument(OpenApiDocument document) - { - document.SetHostDocument(); - } - private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document) { List errors = new(); - errors.AddRange(document.ResolveJsonSchemaReferences()); + errors.AddRange(document.ResolveReferences()); foreach (var item in errors) { diff --git a/src/Microsoft.OpenApi/Services/HostDocumentResolver.cs b/src/Microsoft.OpenApi/Services/HostDocumentResolver.cs deleted file mode 100644 index c11d8fed3..000000000 --- a/src/Microsoft.OpenApi/Services/HostDocumentResolver.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Services -{ - internal class HostDocumentResolver : OpenApiVisitorBase - { - private readonly OpenApiDocument _currentDocument; - - public HostDocumentResolver(OpenApiDocument currentDocument) - { - _currentDocument = currentDocument; - } - - /// - /// Visits the referenceable element in the host document - /// - /// The referenceable element in the doc. - public override void Visit(IOpenApiReferenceable referenceable) - { - if (referenceable.Reference != null) - { - referenceable.Reference.HostDocument = _currentDocument; - } - } - } -} diff --git a/src/Microsoft.OpenApi/Services/ReferenceResolver.cs b/src/Microsoft.OpenApi/Services/ReferenceResolver.cs new file mode 100644 index 000000000..f5d8d626f --- /dev/null +++ b/src/Microsoft.OpenApi/Services/ReferenceResolver.cs @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Json.Schema; +using Microsoft.OpenApi.Exceptions; +using System.Linq; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Extensions; + +namespace Microsoft.OpenApi.Services +{ + internal class ReferenceResolver : OpenApiVisitorBase + { + private readonly OpenApiDocument _currentDocument; + private readonly List _errors = new(); + + public ReferenceResolver(OpenApiDocument currentDocument) + { + _currentDocument = currentDocument; + } + + /// + /// List of errors related to the OpenApiDocument + /// + public IEnumerable Errors => _errors; + + /// + /// Visits the referenceable element in the host document + /// + /// The referenceable element in the doc. + public override void Visit(IOpenApiReferenceable referenceable) + { + if (referenceable.Reference != null) + { + referenceable.Reference.HostDocument = _currentDocument; + } + } + + /// + /// Resolves schemas in components + /// + /// + public override void Visit(OpenApiComponents components) + { + components.Schemas = ResolveJsonSchemas(components.Schemas); + } + + /// + /// Resolve all JsonSchema references used in mediaType object + /// + /// + public override void Visit(OpenApiMediaType mediaType) + { + ResolveJsonSchema(mediaType.Schema, r => mediaType.Schema = r ?? mediaType.Schema); + } + + /// + /// Resolve all JsonSchema references used in a parameter + /// + public override void Visit(OpenApiParameter parameter) + { + ResolveJsonSchema(parameter.Schema, r => parameter.Schema = r); + } + + /// + /// Resolve all references used in a JsonSchema + /// + /// + public override void Visit(ref JsonSchema schema) + { + var reference = schema.GetRef(); + var description = schema.GetDescription(); + var summary = schema.GetSummary(); + + if (schema.Keywords.Count.Equals(1) && reference != null) + { + schema = ResolveJsonSchemaReference(reference, description, summary); + } + + var builder = new JsonSchemaBuilder(); + if (schema?.Keywords is { } keywords) + { + foreach (var keyword in keywords) + { + builder.Add(keyword); + } + } + + ResolveJsonSchema(schema.GetItems(), r => builder.Items(r)); + ResolveJsonSchemaList((IList)schema.GetOneOf(), r => builder.OneOf(r)); + ResolveJsonSchemaList((IList)schema.GetAllOf(), r => builder.AllOf(r)); + ResolveJsonSchemaList((IList)schema.GetAnyOf(), r => builder.AnyOf(r)); + ResolveJsonSchemaMap((IDictionary)schema.GetProperties(), r => builder.Properties((IReadOnlyDictionary)r)); + ResolveJsonSchema(schema.GetAdditionalProperties(), r => builder.AdditionalProperties(r)); + + schema = builder.Build(); + } + + /// + /// Visits an IBaseDocument instance + /// + /// + public override void Visit(IBaseDocument document) { } + + private Dictionary ResolveJsonSchemas(IDictionary schemas) + { + var resolvedSchemas = new Dictionary(); + foreach (var schema in schemas) + { + var schemaValue = schema.Value; + Visit(ref schemaValue); + resolvedSchemas[schema.Key] = schemaValue; + } + + return resolvedSchemas; + } + + /// + /// Resolves the target to a JSON schema reference by retrieval from Schema registry + /// + /// The JSON schema reference. + /// The schema's description. + /// The schema's summary. + /// + public JsonSchema ResolveJsonSchemaReference(Uri reference, string description = null, string summary = null) + { + var resolvedSchema = _currentDocument.ResolveJsonSchemaReference(reference); + + if (resolvedSchema != null) + { + var resolvedSchemaBuilder = new JsonSchemaBuilder(); + + foreach (var keyword in resolvedSchema.Keywords) + { + resolvedSchemaBuilder.Add(keyword); + + // Replace the resolved schema's description with that of the schema reference + if (!string.IsNullOrEmpty(description)) + { + resolvedSchemaBuilder.Description(description); + } + + // Replace the resolved schema's summary with that of the schema reference + if (!string.IsNullOrEmpty(summary)) + { + resolvedSchemaBuilder.Summary(summary); + } + } + + return resolvedSchemaBuilder.Build(); + } + else + { + var referenceId = reference.OriginalString.Split('/').LastOrDefault(); + throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); + } + } + + private void ResolveJsonSchema(JsonSchema schema, Action assign) + { + if (schema == null) return; + var reference = schema.GetRef(); + var description = schema.GetDescription(); + var summary = schema.GetSummary(); + + if (reference != null) + { + assign(ResolveJsonSchemaReference(reference, description, summary)); + } + } + + private void ResolveJsonSchemaList(IList list, Action> assign) + { + if (list == null) return; + + for (int i = 0; i < list.Count; i++) + { + var entity = list[i]; + var reference = entity?.GetRef(); + if (reference != null) + { + list[i] = ResolveJsonSchemaReference(reference); + } + } + + assign(list.ToList()); + } + + private void ResolveJsonSchemaMap(IDictionary map, Action> assign) + { + if (map == null) return; + + foreach (var key in map.Keys.ToList()) + { + var entity = map[key]; + var reference = entity.GetRef(); + if (reference != null) + { + map[key] = ResolveJsonSchemaReference(reference); + } + } + + assign(map.ToDictionary(e => e.Key, e => e.Value)); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index ba99aff49..68cb9057a 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -98,7 +98,7 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() doc.Workspace.RegisterComponents(doc2); doc2.Workspace.RegisterComponents(doc); doc.Workspace.AddDocumentId("common", doc2.BaseUri); - var errors = doc.ResolveJsonSchemaReferences(); + var errors = doc.ResolveReferences(); Assert.Empty(errors); } From 977e8c69700e98f0d4a231bdc99c95f303eedafa Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 4 Apr 2024 20:17:52 +0300 Subject: [PATCH 31/34] Update XML summary --- src/Microsoft.OpenApi/Services/ReferenceResolver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Services/ReferenceResolver.cs b/src/Microsoft.OpenApi/Services/ReferenceResolver.cs index f5d8d626f..ae568c6f1 100644 --- a/src/Microsoft.OpenApi/Services/ReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/ReferenceResolver.cs @@ -12,6 +12,10 @@ namespace Microsoft.OpenApi.Services { + /// + /// This class is used to wallk an OpenApiDocument and sets the host document of OpenApiReferences + /// and resolves JsonSchema references. + /// internal class ReferenceResolver : OpenApiVisitorBase { private readonly OpenApiDocument _currentDocument; @@ -119,7 +123,7 @@ private Dictionary ResolveJsonSchemas(IDictionary - /// Resolves the target to a JSON schema reference by retrieval from Schema registry + /// Resolves the target to a JsonSchema reference by retrieval from Schema registry /// /// The JSON schema reference. /// The schema's description. From 21bed6b6c9849ba2adae14f5b8d251eb7cf4ef18 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 4 Apr 2024 20:18:13 +0300 Subject: [PATCH 32/34] Update PublicAPI --- .../PublicApi/PublicApi.approved.txt | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 6d2b7a4d3..c88cf5dc8 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -451,7 +451,7 @@ namespace Microsoft.OpenApi.Models public const string AuthorizationCode = "authorizationCode"; public const string AuthorizationUrl = "authorizationUrl"; public const string BasePath = "basePath"; - public const string BaseRegistryUri = "http://openapi.net/"; + public const string BaseRegistryUri = "https://openapi.net/"; public const string Basic = "basic"; public const string Bearer = "bearer"; public const string BearerFormat = "bearerFormat"; @@ -624,7 +624,6 @@ namespace Microsoft.OpenApi.Models public void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public void SerializeAsV31(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } - public void SetHostDocument() { } public static string GenerateHashValue(Microsoft.OpenApi.Models.OpenApiDocument doc) { } public static Microsoft.OpenApi.Reader.ReadResult Load(string url, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) { } public static Microsoft.OpenApi.Reader.ReadResult Load(System.IO.Stream stream, string format, Microsoft.OpenApi.Reader.OpenApiReaderSettings settings = null) { } @@ -1381,6 +1380,18 @@ namespace Microsoft.OpenApi.Services public string Response { get; set; } public string ServerVariable { get; } } + public class JsonSchemaReferenceResolver : Microsoft.OpenApi.Services.OpenApiVisitorBase + { + public JsonSchemaReferenceResolver(Microsoft.OpenApi.Models.OpenApiDocument currentDocument) { } + public System.Collections.Generic.IEnumerable Errors { get; } + public Json.Schema.JsonSchema ResolveJsonSchemaReference(System.Uri reference, string description = null, string summary = null) { } + public override void Visit(Json.Schema.IBaseDocument document) { } + public override void Visit(ref Json.Schema.JsonSchema schema) { } + public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiReferenceable referenceable) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiComponents components) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiMediaType mediaType) { } + public override void Visit(Microsoft.OpenApi.Models.OpenApiParameter parameter) { } + } public enum MermaidNodeShape { SquareCornerRectangle = 0, @@ -1405,28 +1416,6 @@ namespace Microsoft.OpenApi.Services public OpenApiReferenceError(Microsoft.OpenApi.Exceptions.OpenApiException exception) { } public OpenApiReferenceError(Microsoft.OpenApi.Models.OpenApiReference reference, string message) { } } - public class OpenApiReferenceResolver : Microsoft.OpenApi.Services.OpenApiVisitorBase - { - public OpenApiReferenceResolver(Microsoft.OpenApi.Models.OpenApiDocument currentDocument, bool resolveRemoteReferences = true) { } - public System.Collections.Generic.IEnumerable Errors { get; } - public Json.Schema.JsonSchema ResolveJsonSchemaReference(System.Uri reference, string description = null, string summary = null) { } - public override void Visit(Json.Schema.IBaseDocument document) { } - public override void Visit(ref Json.Schema.JsonSchema schema) { } - public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiReferenceable referenceable) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiComponents components) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiDocument doc) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiMediaType mediaType) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiOperation operation) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiParameter parameter) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiResponses responses) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiSecurityRequirement securityRequirement) { } - public override void Visit(System.Collections.Generic.IDictionary callbacks) { } - public override void Visit(System.Collections.Generic.IDictionary examples) { } - public override void Visit(System.Collections.Generic.IDictionary headers) { } - public override void Visit(System.Collections.Generic.IDictionary links) { } - public override void Visit(System.Collections.Generic.IDictionary webhooks) { } - public override void Visit(System.Collections.Generic.IList parameters) { } - } public class OpenApiUrlTreeNode { public static readonly System.Collections.Generic.IReadOnlyDictionary MermaidNodeStyles; From 54712c138db83d1758af70481db4f295fbafa674 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 4 Apr 2024 20:35:01 +0300 Subject: [PATCH 33/34] Update Public API --- .../Services/JsonSchemaReferenceResolver.cs | 215 ------------------ .../PublicApi/PublicApi.approved.txt | 12 - 2 files changed, 227 deletions(-) delete mode 100644 src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs diff --git a/src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs b/src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs deleted file mode 100644 index 845e50556..000000000 --- a/src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using Json.Schema; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Extensions; -using Microsoft.OpenApi.Interfaces; -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Services -{ - /// - /// This class is used to walk an OpenApiDocument and convert unresolved references to references to populated objects - /// - public class JsonSchemaReferenceResolver : OpenApiVisitorBase - { - private readonly OpenApiDocument _currentDocument; - private readonly List _errors = new(); - - /// - /// Initializes the class. - /// - public JsonSchemaReferenceResolver(OpenApiDocument currentDocument) - { - _currentDocument = currentDocument; - } - - /// - /// List of errors related to the OpenApiDocument - /// - public IEnumerable Errors => _errors; - - /// - /// Visits the referenceable element in the host document - /// - /// The referenceable element in the doc. - public override void Visit(IOpenApiReferenceable referenceable) - { - if (referenceable.Reference != null) - { - referenceable.Reference.HostDocument = _currentDocument; - } - } - - /// - /// Resolves schemas in components - /// - /// - public override void Visit(OpenApiComponents components) - { - components.Schemas = ResolveJsonSchemas(components.Schemas); - } - - /// - /// Resolve all JsonSchema references used in mediaType object - /// - /// - public override void Visit(OpenApiMediaType mediaType) - { - ResolveJsonSchema(mediaType.Schema, r => mediaType.Schema = r ?? mediaType.Schema); - } - - /// - /// Resolve all JsonSchema references used in a parameter - /// - public override void Visit(OpenApiParameter parameter) - { - ResolveJsonSchema(parameter.Schema, r => parameter.Schema = r); - } - - /// - /// Resolve all references used in a JsonSchema - /// - /// - public override void Visit(ref JsonSchema schema) - { - var reference = schema.GetRef(); - var description = schema.GetDescription(); - var summary = schema.GetSummary(); - - if (schema.Keywords.Count.Equals(1) && reference != null) - { - schema = ResolveJsonSchemaReference(reference, description, summary); - } - - var builder = new JsonSchemaBuilder(); - if (schema?.Keywords is { } keywords) - { - foreach (var keyword in keywords) - { - builder.Add(keyword); - } - } - - ResolveJsonSchema(schema.GetItems(), r => builder.Items(r)); - ResolveJsonSchemaList((IList)schema.GetOneOf(), r => builder.OneOf(r)); - ResolveJsonSchemaList((IList)schema.GetAllOf(), r => builder.AllOf(r)); - ResolveJsonSchemaList((IList)schema.GetAnyOf(), r => builder.AnyOf(r)); - ResolveJsonSchemaMap((IDictionary)schema.GetProperties(), r => builder.Properties((IReadOnlyDictionary)r)); - ResolveJsonSchema(schema.GetAdditionalProperties(), r => builder.AdditionalProperties(r)); - - schema = builder.Build(); - } - - /// - /// Visits an IBaseDocument instance - /// - /// - public override void Visit(IBaseDocument document) { } - - private Dictionary ResolveJsonSchemas(IDictionary schemas) - { - var resolvedSchemas = new Dictionary(); - foreach (var schema in schemas) - { - var schemaValue = schema.Value; - Visit(ref schemaValue); - resolvedSchemas[schema.Key] = schemaValue; - } - - return resolvedSchemas; - } - - /// - /// Resolves the target to a JSON schema reference by retrieval from Schema registry - /// - /// The JSON schema reference. - /// The schema's description. - /// The schema's summary. - /// - public JsonSchema ResolveJsonSchemaReference(Uri reference, string description = null, string summary = null) - { - var resolvedSchema = _currentDocument.ResolveJsonSchemaReference(reference); - - if (resolvedSchema != null) - { - var resolvedSchemaBuilder = new JsonSchemaBuilder(); - - foreach (var keyword in resolvedSchema.Keywords) - { - resolvedSchemaBuilder.Add(keyword); - - // Replace the resolved schema's description with that of the schema reference - if (!string.IsNullOrEmpty(description)) - { - resolvedSchemaBuilder.Description(description); - } - - // Replace the resolved schema's summary with that of the schema reference - if (!string.IsNullOrEmpty(summary)) - { - resolvedSchemaBuilder.Summary(summary); - } - } - - return resolvedSchemaBuilder.Build(); - } - else - { - var referenceId = reference.OriginalString.Split('/').LastOrDefault(); - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId)); - } - } - - private void ResolveJsonSchema(JsonSchema schema, Action assign) - { - if (schema == null) return; - var reference = schema.GetRef(); - var description = schema.GetDescription(); - var summary = schema.GetSummary(); - - if (reference != null) - { - assign(ResolveJsonSchemaReference(reference, description, summary)); - } - } - - private void ResolveJsonSchemaList(IList list, Action> assign) - { - if (list == null) return; - - for (int i = 0; i < list.Count; i++) - { - var entity = list[i]; - var reference = entity?.GetRef(); - if (reference != null) - { - list[i] = ResolveJsonSchemaReference(reference); - } - } - - assign(list.ToList()); - } - - private void ResolveJsonSchemaMap(IDictionary map, Action> assign) - { - if (map == null) return; - - foreach (var key in map.Keys.ToList()) - { - var entity = map[key]; - var reference = entity.GetRef(); - if (reference != null) - { - map[key] = ResolveJsonSchemaReference(reference); - } - } - - assign(map.ToDictionary(e => e.Key, e => e.Value)); - } - } -} diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index df91e1e6d..a9e086061 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1382,18 +1382,6 @@ namespace Microsoft.OpenApi.Services public string Response { get; set; } public string ServerVariable { get; } } - public class JsonSchemaReferenceResolver : Microsoft.OpenApi.Services.OpenApiVisitorBase - { - public JsonSchemaReferenceResolver(Microsoft.OpenApi.Models.OpenApiDocument currentDocument) { } - public System.Collections.Generic.IEnumerable Errors { get; } - public Json.Schema.JsonSchema ResolveJsonSchemaReference(System.Uri reference, string description = null, string summary = null) { } - public override void Visit(Json.Schema.IBaseDocument document) { } - public override void Visit(ref Json.Schema.JsonSchema schema) { } - public override void Visit(Microsoft.OpenApi.Interfaces.IOpenApiReferenceable referenceable) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiComponents components) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiMediaType mediaType) { } - public override void Visit(Microsoft.OpenApi.Models.OpenApiParameter parameter) { } - } public enum MermaidNodeShape { SquareCornerRectangle = 0, From 11b3399885484688e53def447cf3ae52474f7540 Mon Sep 17 00:00:00 2001 From: Irvine Sunday Date: Thu, 4 Apr 2024 21:22:41 +0300 Subject: [PATCH 34/34] Update comment --- .../Reader/Services/OpenApiRemoteReferenceCollector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs index 6a80941a5..4d44b98a9 100644 --- a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs +++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.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; @@ -17,7 +17,7 @@ internal class OpenApiRemoteReferenceCollector : OpenApiVisitorBase private readonly Dictionary _references = new(); /// - /// List of all internal and external references collected from OpenApiDocument + /// List of all external references collected from OpenApiDocument /// public IEnumerable References { @@ -37,7 +37,7 @@ public override void Visit(IOpenApiReferenceable referenceable) } /// - /// Collect internal and external references + /// Collect external references /// private void AddExternalReferences(OpenApiReference reference) {