diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 34b546352..5dcf17f7a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -630,6 +630,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 = "https://openapi.net/"; + #region V2.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 42b6734f7..28ed47325 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Json.Schema; -using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Services; @@ -94,8 +94,12 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo /// /// Parameter-less constructor /// - public OpenApiDocument() { } - + public OpenApiDocument() + { + Workspace = new OpenApiWorkspace(); + BaseUri = new(OpenApiConstants.BaseRegistryUri + Guid.NewGuid().ToString()); + } + /// /// Initializes a copy of an an object /// @@ -441,30 +445,17 @@ 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 ResolveReferences() { - var resolver = new OpenApiReferenceResolver(this, false); + var resolver = new ReferenceResolver(this); var walker = new OpenApiWalker(resolver); walker.Walk(this); return resolver.Errors; } - /// - /// Walks the OpenApiDocument and sets the host document for all referenceable objects - /// - public void SetHostDocument() - { - var resolver = new HostDocumentResolver(this); - var walker = new OpenApiWalker(resolver); - walker.Walk(this); - } - /// /// Load the referenced object from a object /// @@ -488,6 +479,33 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) return ResolveReference(reference, false); } + /// + /// Resolves JsonSchema refs + /// + /// + /// A JsonSchema ref. + 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 + uriLocation = BaseUri + relativePath; + } + else + { + // External reference + var externalUri = referenceUri.OriginalString.Split('#').First(); + var externalDocId = Workspace.GetDocumentId(externalUri); + uriLocation = externalDocId + relativePath; + } + + return (JsonSchema)Workspace.ResolveReference(uriLocation); + } + /// /// Takes in an OpenApi document instance and generates its hash value /// @@ -532,16 +550,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 (this.Workspace == null) - { - throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution); - } - return this.Workspace.ResolveReference(reference); - } - if (!reference.Type.HasValue) { throw new ArgumentException(Properties.SRResource.LocalReferenceRequiresType); @@ -562,51 +570,16 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool return null; } - if (this.Components == null) - { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); - } - - try - { - switch (reference.Type) - { - case ReferenceType.PathItem: - return Components.PathItems[reference.Id]; - case ReferenceType.Response: - return Components.Responses[reference.Id]; - - case ReferenceType.Parameter: - return Components.Parameters[reference.Id]; - - case ReferenceType.Example: - return Components.Examples[reference.Id]; - - case ReferenceType.RequestBody: - return Components.RequestBodies[reference.Id]; + string uriLocation; + string relativePath = "/components/" + reference.Type.GetDisplayName() + "/" + reference.Id; - case ReferenceType.Header: - return Components.Headers[reference.Id]; + uriLocation = useExternal + ? Workspace.GetDocumentId(reference.ExternalResource)?.OriginalString + relativePath + : BaseUri + relativePath; - case ReferenceType.SecurityScheme: - return Components.SecuritySchemes[reference.Id]; - - case ReferenceType.Link: - return Components.Links[reference.Id]; - - case ReferenceType.Callback: - return Components.Callbacks[reference.Id]; - - default: - throw new OpenApiException(Properties.SRResource.InvalidReferenceType); - } - } - catch (KeyNotFoundException) - { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); - } + return Workspace.ResolveReference(uriLocation); } - + /// /// Parses a local file path or Url into an Open API document. /// @@ -707,12 +680,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/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index b32810a64..648004ab4 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 048e29cb5..29003da51 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -168,9 +168,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/OpenApiCallbackReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs index 0a28deab4..834e6aa3b 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs @@ -33,7 +33,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/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..07fd6bfff 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 { @@ -93,7 +95,7 @@ public async Task ReadAsync(JsonNode jsonNode, } } - SetHostDocument(document); + ResolveReferences(diagnostic, document); } catch (OpenApiException ex) { @@ -198,9 +200,15 @@ private async Task LoadExternalRefs(OpenApiDocument document, return await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document, format ?? OpenApiConstants.Json, null, cancellationToken); } - private void SetHostDocument(OpenApiDocument document) + private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document) { - document.SetHostDocument(); + List errors = new(); + errors.AddRange(document.ResolveReferences()); + + foreach (var item in errors) + { + diagnostic.Errors.Add(item); + } } } } diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiRemoteReferenceCollector.cs index 135e69eee..4d44b98a9 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. +// 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; @@ -16,7 +17,7 @@ internal class OpenApiRemoteReferenceCollector : OpenApiVisitorBase private readonly Dictionary _references = new(); /// - /// List of external references collected from OpenApiDocument + /// List of all external references collected from OpenApiDocument /// public IEnumerable References { @@ -32,13 +33,13 @@ public IEnumerable References /// public override void Visit(IOpenApiReferenceable referenceable) { - AddReference(referenceable.Reference); + AddExternalReferences(referenceable.Reference); } /// - /// Collect external reference + /// Collect external references /// - private void AddReference(OpenApiReference reference) + private void AddExternalReferences(OpenApiReference reference) { if (reference is {IsExternal: true} && !_references.ContainsKey(reference.ExternalResource)) diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs index d80ffd714..abed56b2c 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 @@ -39,6 +40,7 @@ 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)) { @@ -51,7 +53,7 @@ internal async Task LoadAsync(OpenApiReference reference, } if (result.OpenApiDocument != null) { - var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken); + 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 477f05f0d..0f814616f 100644 --- a/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs @@ -224,36 +224,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); - RegisterComponentsSchemasInGlobalRegistry(openApidoc.Components?.Schemas); + FixRequestBodyReferences(openApiDoc); - return openApidoc; + // Register components + openApiDoc.Workspace.RegisterComponents(openApiDoc); + + return openApiDoc; } private static void ProcessResponsesMediaTypes(MapNode mapNode, IEnumerable responses, ParsingContext context) diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiComponentsDeserializer.cs index 1474c81a1..a6ca78101 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiComponentsDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiComponentsDeserializer.cs @@ -43,13 +43,6 @@ public static OpenApiComponents LoadComponents(ParseNode node, OpenApiDocument h 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/Reader/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs index 55174f34e..3ed838de9 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 { @@ -47,11 +48,14 @@ internal static partial class OpenApiV3Deserializer public static OpenApiDocument LoadOpenApi(RootNode rootNode) { - var openApiNode = rootNode.GetMap(); var openApiDoc = new OpenApiDocument(); + var openApiNode = rootNode.GetMap(); ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); + // Register components + openApiDoc.Workspace.RegisterComponents(openApiDoc); + return openApiDoc; } } diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiComponentsDeserializer.cs index a1a399bd2..278c2043e 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiComponentsDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiComponentsDeserializer.cs @@ -42,12 +42,6 @@ public static OpenApiComponents LoadComponents(ParseNode node, OpenApiDocument h 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/Reader/V31/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs index c868ab497..e4de78613 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 { @@ -9,7 +11,7 @@ namespace Microsoft.OpenApi.Reader.V31 /// runtime Open API object model. /// internal static partial class OpenApiV31Deserializer - { + { private static readonly FixedFieldMap _openApiFixedFields = new() { { @@ -45,11 +47,14 @@ internal static partial class OpenApiV31Deserializer public static OpenApiDocument LoadOpenApi(RootNode rootNode) { - var openApiNode = rootNode.GetMap(); var openApiDoc = new OpenApiDocument(); + var openApiNode = rootNode.GetMap(); ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields); + // Register components + openApiDoc.Workspace.RegisterComponents(openApiDoc); + return openApiDoc; } } 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/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/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs deleted file mode 100644 index 43f1b7877..000000000 --- a/src/Microsoft.OpenApi/Services/OpenApiReferenceResolver.cs +++ /dev/null @@ -1,447 +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 refUri = $"https://registry{reference.OriginalString.Split('#').LastOrDefault()}"; - var resolvedSchema = (JsonSchema)SchemaRegistry.Global.Get(new Uri(refUri)); - - 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/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs index 63c1defaf..ca3fb32d0 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Json.Schema; -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 _documents = new(); - private readonly Dictionary _fragments = new(); - private readonly Dictionary _schemaFragments = new(); - private readonly Dictionary _artifacts = new(); - - /// - /// A list of OpenApiDocuments contained in the workspace - /// - public IEnumerable Documents - { - get - { - return _documents.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,133 +48,119 @@ 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 _documents.ContainsKey(key) || _fragments.ContainsKey(key) || _artifacts.ContainsKey(key); + return _IOpenApiReferenceableRegistry.Count + _jsonSchemaRegistry.Count + _artifactsRegistry.Count; } /// - /// Add an OpenApiDocument to the workspace. + /// Registers a component in the component registry. /// /// - /// - 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; - _documents.Add(ToLocationUrl(location), document); + var uri = ToLocationUrl(location); + if (component is IBaseDocument schema) + { + 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) { - _fragments.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) { - _schemaFragments.Add(ToLocationUrl(location), fragment); + if (_documentsIdRegistry.TryGetValue(key, out var id)) + { + return id; + } + return null; } /// - /// Add a stream based artificat 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) { - _artifacts.Add(ToLocationUrl(location), artifact); + var key = ToLocationUrl(location); + return _IOpenApiReferenceableRegistry.ContainsKey(key) || _jsonSchemaRegistry.ContainsKey(key) || _artifactsRegistry.ContainsKey(key); } /// - /// Returns the target of an OpenApiReference from within the workspace. + /// Resolves a reference given a key. /// - /// An instance of an OpenApiReference - /// - public IOpenApiReferenceable ResolveReference(OpenApiReference reference) + /// + /// + /// The resolved reference. + public T ResolveReference(string location) { - if (_documents.TryGetValue(new(BaseUrl, reference.ExternalResource), out var doc)) + if (string.IsNullOrEmpty(location)) return default; + + var uri = ToLocationUrl(location); + if (_IOpenApiReferenceableRegistry.TryGetValue(uri, out var referenceableValue)) { - return doc.ResolveReference(reference, false); + return (T)referenceableValue; } - else if (_fragments.TryGetValue(new(BaseUrl, reference.ExternalResource), out var fragment)) + else if (_jsonSchemaRegistry.TryGetValue(uri, out var schemaValue)) { - var jsonPointer = new JsonPointer($"/{reference.Id ?? string.Empty}"); - return fragment.ResolveReference(jsonPointer); + return (T)schemaValue; } - return null; - } - - /// - /// Resolve the target of a JSON schema reference from within the workspace - /// - /// An instance of a JSON schema reference. - /// - public JsonSchema ResolveJsonSchemaReference(Uri reference) - { - var docs = _documents.Values; - if (docs.Any()) + else if (_artifactsRegistry.TryGetValue(uri, out var artifact)) { - 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; + return (T)(object)artifact; } - else - { - foreach (var jsonSchema in _schemaFragments) - { - SchemaRegistry.Global.Register(reference, jsonSchema.Value); - } - return FetchSchemaFromRegistry(reference); - } - } - - /// - /// - /// - /// - /// - public Stream GetArtifact(string location) - { - return _artifacts[ToLocationUrl(location)]; + return default; } private Uri ToLocationUrl(string location) { return new(BaseUrl, location); } - - private static JsonSchema FetchSchemaFromRegistry(Uri reference) - { - var resolvedSchema = (JsonSchema)SchemaRegistry.Global.Get(reference); - return resolvedSchema; - } } } diff --git a/src/Microsoft.OpenApi/Services/ReferenceResolver.cs b/src/Microsoft.OpenApi/Services/ReferenceResolver.cs new file mode 100644 index 000000000..ae568c6f1 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/ReferenceResolver.cs @@ -0,0 +1,213 @@ +// 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 +{ + /// + /// 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; + 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 JsonSchema 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.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 2eb86e4e6..9ec7afb3a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -57,10 +57,10 @@ 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 + 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/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index aa8013bc2..868d4c52f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -59,14 +59,13 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo { LoadExternalRefs = true, CustomExternalLoader = new ResourceLoader(), - BaseUrl = new("fie://c:\\") + BaseUrl = new("file://c:\\"), }; ReadResult result; 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"] @@ -75,6 +74,10 @@ 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 referencedParameter = result.OpenApiDocument .Paths["/todos"] .Operations[OperationType.Get] diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index 99359881c..7cbd961fc 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; @@ -100,6 +100,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() } } }, options => options.Excluding(x => x.Reference) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs index ee9e1f401..b3e30c672 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.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.IO; @@ -18,15 +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("yaml", new OpenApiYamlReader()); + 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).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 d864f597d..611f2c3d5 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 @@ -39,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 @@ -148,7 +151,8 @@ public void ShouldParseProducesInAnyOrder() ["Error"] = errorSchema } } - }); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + } [Fact] @@ -165,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))); @@ -176,7 +181,6 @@ public void ShouldAssignSchemaToAllResponses() var json = response.Value.Content["application/json"]; Assert.NotNull(json); - Assert.Equal(json.Schema.Keywords.Count, targetSchema.Keywords.Count); var xml = response.Value.Content["application/xml"]; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index d11a87d7b..087220fa7 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -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,10 +181,9 @@ 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); + actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); } [Fact] @@ -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,10 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() }; // Assert - actual.OpenApiDocument.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Webhooks["pets"].Reference)); + 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 b337a5166..21d7e2884 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -96,11 +96,11 @@ public void ParseDocumentFromInlineStringShouldSucceed() Version = "0.9.1" }, Paths = new OpenApiPaths() - }); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); 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() { @@ -147,7 +147,7 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() } }, Paths = new OpenApiPaths() - }); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); } [Fact] public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() @@ -163,7 +163,7 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() Version = "0.9" }, Paths = new OpenApiPaths() - }); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic @@ -191,7 +191,7 @@ public void ParseMinimalDocumentShouldSucceed() Version = "0.9.1" }, Paths = new OpenApiPaths() - }); + }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); result.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() @@ -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 } } } @@ -508,16 +512,17 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Components = components }; - result.OpenApiDocument.Should().BeEquivalentTo(expectedDoc); + 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 @@ -927,12 +939,14 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() .Excluding(x => x.Paths["/pets"].Operations[OperationType.Get].Tags[0].Reference.HostDocument) .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.Paths["/pets"].Operations[OperationType.Post].Tags[1].Reference.HostDocument) + .Excluding(x => x.Workspace) + .Excluding(y => y.BaseUri)); actual.OpenApiDiagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } + [Fact] public void ParsePetStoreExpandedShouldSucceed() { @@ -1046,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] @@ -1099,9 +1119,9 @@ public void ParseDocWithRefsUsingProxyReferencesSucceeds() .Format("int32") .Default(10), Reference = new OpenApiReference - { - Id = "LimitParameter", - Type = ReferenceType.Parameter + { + Id = "LimitParameter", + Type = ReferenceType.Parameter } } ], @@ -1126,7 +1146,7 @@ public void ParseDocWithRefsUsingProxyReferencesSucceeds() .Default(10) } } - } + } }; var expectedSerializedDoc = @"openapi: 3.0.1 @@ -1163,6 +1183,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.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 38a39beae..3a16f4d2a 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; @@ -18,11 +19,14 @@ namespace Microsoft.OpenApi.Tests.Models.References [Collection("DefaultSettings")] 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 +61,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 +106,57 @@ 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() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - OpenApiDocument openApiDoc = OpenApiDocument.Parse(OpenApi, "yaml").OpenApiDocument; - OpenApiDocument openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, "yaml").OpenApiDocument; - openApiDoc_2.Workspace = new(); - openApiDoc_2.Workspace.AddDocument("http://localhost/callbackreference", openApiDoc); - _localCallbackReference = new("callbackEvent", openApiDoc); - _externalCallbackReference = new("callbackEvent", openApiDoc_2, "http://localhost/callbackreference"); + OpenApiDocument openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + OpenApiDocument openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", openApiDoc_2.BaseUri); + openApiDoc.Workspace.RegisterComponents(openApiDoc_2); + _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] @@ -152,10 +166,10 @@ 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 - _localCallbackReference.SerializeAsV3(writer); + _externalCallbackReference.SerializeAsV3(writer); writer.Flush(); // Assert @@ -169,10 +183,10 @@ 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 - _localCallbackReference.SerializeAsV31(writer); + _externalCallbackReference.SerializeAsV31(writer); writer.Flush(); // Assert 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/OpenApiExampleReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs index a48ffd906..28a91aa8e 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; @@ -18,11 +19,14 @@ namespace Microsoft.OpenApi.Tests.Models.References [Collection("DefaultSettings")] 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 +39,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 +87,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; @@ -86,18 +113,18 @@ public class OpenApiExampleReferenceTests public OpenApiExampleReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, "yaml").OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, "yaml").OpenApiDocument; - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/examplereference", _openApiDoc); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); - _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 +135,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.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.SerializeHeaderReferenceAsV2JsonWorksAsync_produceTerseOutput=False.verified.txt index 8bd613186..b957bd951 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": "The URL of the newly 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 9d510cb80..17f59471d 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":"The URL of the newly 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/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/OpenApiHeaderReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs index 74d8e5797..e55acf5f3 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs @@ -5,11 +5,13 @@ 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.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -18,11 +20,14 @@ namespace Microsoft.OpenApi.Tests.Models.References [Collection("DefaultSettings")] 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 +37,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 +67,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; @@ -66,19 +83,19 @@ public class OpenApiHeaderReferenceTests public OpenApiHeaderReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, "yaml").OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, "yaml").OpenApiDocument; - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/headerreference", _openApiDoc); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); - _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 +103,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.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/OpenApiLinkReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs index 9a52b5234..87d2db06e 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; @@ -18,11 +19,14 @@ namespace Microsoft.OpenApi.Tests.Models.References [Collection("DefaultSettings")] 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 +53,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 +100,21 @@ 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} + schemas: + User: + type: object + properties: + id: + type: integer + name: + type: string "; private readonly OpenApiLinkReference _localLinkReference; @@ -100,17 +125,17 @@ public class OpenApiLinkReferenceTests public OpenApiLinkReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, "yaml").OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, "yaml").OpenApiDocument; - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/linkreferencesample", _openApiDoc); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); - _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 +145,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.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/OpenApiParameterReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs index 8f76fc526..c00db94f5 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; @@ -18,37 +19,42 @@ namespace Microsoft.OpenApi.Tests.Models.References [Collection("DefaultSettings")] 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 +64,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; @@ -67,17 +83,17 @@ public class OpenApiParameterReferenceTests public OpenApiParameterReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, "yaml").OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, "yaml").OpenApiDocument; - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/parameterreference", _openApiDoc); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); - _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 +105,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.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 d4aba67c3..2d7354f78 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; @@ -23,10 +24,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.1.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 +72,6 @@ public class OpenApiPathItemReferenceTests description: User deleted successfully "; - private const string OpenApi_2 = @" -openapi: 3.1.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; @@ -67,18 +80,19 @@ public class OpenApiPathItemReferenceTests public OpenApiPathItemReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, "yaml").OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, "yaml").OpenApiDocument; - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/pathitemreference", _openApiDoc); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); + _openApiDoc_2.Workspace.RegisterComponents(_openApiDoc_2); - _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 +103,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 dd417f093..b6467d1c1 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; @@ -12,6 +12,7 @@ using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Writers; +using Microsoft.OpenApi.Services; using VerifyXunit; using Xunit; @@ -25,34 +26,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 +53,8 @@ public class OpenApiRequestBodyReferenceTests info: title: Sample API version: 1.0.0 - +servers: + - url: https://myserver.com/beta paths: /users: post: @@ -70,6 +64,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; @@ -80,22 +90,40 @@ public class OpenApiRequestBodyReferenceTests public OpenApiRequestBodyReferenceTests() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, "yaml").OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, "yaml").OpenApiDocument; - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/requestbodyreference", _openApiDoc); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); - _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" }; } + [Fact] + public void RequestBodyReferenceResolutionWorks() + { + // Assert + 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_2.Components.RequestBodies.First().Value.Description); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs index f460374a8..42d0532e7 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; @@ -24,22 +25,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 = @" @@ -47,13 +40,28 @@ 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' + schemas: + Pong: + type: object + properties: + sound: + type: string "; private readonly OpenApiResponseReference _localResponseReference; @@ -64,17 +72,17 @@ public class OpenApiResponseReferenceTest public OpenApiResponseReferenceTest() { OpenApiReaderRegistry.RegisterReader(OpenApiConstants.Yaml, new OpenApiYamlReader()); - _openApiDoc = OpenApiDocument.Parse(OpenApi, "yaml").OpenApiDocument; - _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, "yaml").OpenApiDocument; - _openApiDoc_2.Workspace = new(); - _openApiDoc_2.Workspace.AddDocument("http://localhost/responsereference", _openApiDoc); + _openApiDoc = OpenApiDocument.Parse(OpenApi, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc_2 = OpenApiDocument.Parse(OpenApi_2, OpenApiConstants.Yaml).OpenApiDocument; + _openApiDoc.Workspace.AddDocumentId("https://myserver.com/beta", _openApiDoc_2.BaseUri); + _openApiDoc.Workspace.RegisterComponents(_openApiDoc_2); - _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" }; @@ -84,11 +92,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 dccd41692..7fcd7dfd8 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: diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 8cce0b6f5..a9e086061 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -451,6 +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 = "https://openapi.net/"; public const string Basic = "basic"; public const string Bearer = "bearer"; public const string BearerFormat = "bearerFormat"; @@ -618,12 +619,12 @@ 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) { } 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) { } @@ -1405,28 +1406,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; @@ -1508,18 +1487,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 Json.Schema.JsonSchema ResolveJsonSchemaReference(System.Uri reference) { } - public Microsoft.OpenApi.Interfaces.IOpenApiReferenceable ResolveReference(Microsoft.OpenApi.Models.OpenApiReference reference) { } + 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 57faaf72f..68cb9057a 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -14,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() { @@ -56,8 +43,9 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther() } } } - }); - workspace.AddDocument("common", new OpenApiDocument() + }; + + var doc2 = new OpenApiDocument() { Components = new OpenApiComponents() { @@ -65,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] @@ -74,13 +65,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(); + + workspace.RegisterComponent("https://everything.json/common#/components/schemas/test", externalDoc.Components.Schemas["test"]); - var schema = workspace.ResolveJsonSchemaReference(refUri); - + var schema = workspace.ResolveReference("https://everything.json/common#/components/schemas/test"); + Assert.NotNull(schema); Assert.Equal("The referenced one", schema.GetDescription()); } @@ -88,10 +78,8 @@ public void OpenApiWorkspacesCanResolveExternalReferences() [Fact] 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"; @@ -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(refUri)); + 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,10 +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("fragment", schemaFragment); + workspace.RegisterComponent("common#/components/schemas/test", schemaFragment); // Act - var schema = workspace.ResolveJsonSchemaReference(new Uri("https://everything.json/common#/components/schemas/test")); + var schema = workspace.ResolveReference("common#/components/schemas/test"); // Assert Assert.NotNull(schema); @@ -167,21 +138,18 @@ 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 - private static OpenApiDocument CreateCommonDocument(Uri refUri) + private static OpenApiDocument CreateCommonDocument() { var doc = new OpenApiDocument() { @@ -193,11 +161,6 @@ private static OpenApiDocument CreateCommonDocument(Uri refUri) } }; - foreach(var schema in doc.Components.Schemas) - { - SchemaRegistry.Global.Register(refUri, schema.Value); - } - return doc; } }