diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 28ed47325..78ef581ed 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -446,14 +446,12 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList /// /// Walks the OpenApiDocument and sets the host document for all IOpenApiReferenceable objects - /// and resolves JsonSchema references /// - public IEnumerable ResolveReferences() + public void SetReferenceHostDocument() { - var resolver = new ReferenceResolver(this); + var resolver = new ReferenceHostDocumentSetter(this); var walker = new OpenApiWalker(resolver); walker.Walk(this); - return resolver.Errors; } /// diff --git a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs index 07fd6bfff..b01a5644e 100644 --- a/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs +++ b/src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs @@ -95,7 +95,7 @@ public async Task ReadAsync(JsonNode jsonNode, } } - ResolveReferences(diagnostic, document); + document.SetReferenceHostDocument(); } catch (OpenApiException ex) { @@ -199,16 +199,5 @@ private async Task LoadExternalRefs(OpenApiDocument document, var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, settings.CustomExternalLoader ?? streamLoader, settings); return await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document, format ?? OpenApiConstants.Json, null, cancellationToken); } - - private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document) - { - List errors = new(); - errors.AddRange(document.ResolveReferences()); - - foreach (var item in errors) - { - diagnostic.Errors.Add(item); - } - } } } diff --git a/src/Microsoft.OpenApi/Services/ReferenceResolver.cs b/src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs similarity index 90% rename from src/Microsoft.OpenApi/Services/ReferenceResolver.cs rename to src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs index ae568c6f1..87e493b3c 100644 --- a/src/Microsoft.OpenApi/Services/ReferenceResolver.cs +++ b/src/Microsoft.OpenApi/Services/JsonSchemaReferenceResolver.cs @@ -6,22 +6,20 @@ 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. + /// This class is used to walk an OpenApiDocument and resolves JsonSchema references. /// - internal class ReferenceResolver : OpenApiVisitorBase + internal class JsonSchemaReferenceResolver : OpenApiVisitorBase { private readonly OpenApiDocument _currentDocument; private readonly List _errors = new(); - public ReferenceResolver(OpenApiDocument currentDocument) + public JsonSchemaReferenceResolver(OpenApiDocument currentDocument) { _currentDocument = currentDocument; } @@ -31,18 +29,6 @@ public ReferenceResolver(OpenApiDocument currentDocument) /// 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 /// diff --git a/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs b/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs new file mode 100644 index 000000000..1d9bb8e8e --- /dev/null +++ b/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// This class is used to walk an OpenApiDocument and sets the host document of IOpenApiReferenceable objects + /// + internal class ReferenceHostDocumentSetter : OpenApiVisitorBase + { + private readonly OpenApiDocument _currentDocument; + + public ReferenceHostDocumentSetter(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/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 9ec7afb3a..cdc793632 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -54,13 +54,11 @@ public async Task DiagnosticReportMergedForExternalReference() ReadResult result; result = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoMain.yaml", settings); - Assert.NotNull(result); Assert.NotNull(result.OpenApiDocument.Workspace); 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'.")) + new OpenApiError("", "[File: ./TodoReference.yaml] Paths is a REQUIRED field at #/") }); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index 868d4c52f..128430218 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -52,7 +52,7 @@ public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoW } [Fact] - public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWorkspace() + public async Task LoadDocumentWithExternalReferenceShouldLoadExternalDocumentComponentsIntoWorkspace() { // Create a reader that will resolve all references var settings = new OpenApiReaderSettings @@ -63,28 +63,16 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo }; ReadResult result; - result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings); + result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings); - Assert.NotNull(result.OpenApiDocument.Workspace); - - var referencedSchema = result.OpenApiDocument - .Paths["/todos"] - .Operations[OperationType.Get] - .Responses["200"] - .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] - .Parameters.Select(p => p) - .FirstOrDefault(p => p.Name == "filter"); + var externalDocBaseUri = result.OpenApiDocument.Workspace.GetDocumentId("./TodoComponents.yaml"); + var schemasPath = "/components/schemas/"; + var parametersPath = "/components/parameters/"; - Assert.Equal(SchemaValueType.String, referencedParameter.Schema.GetJsonType()); + Assert.NotNull(externalDocBaseUri); + Assert.True(result.OpenApiDocument.Workspace.Contains(externalDocBaseUri + schemasPath + "todo")); + Assert.True(result.OpenApiDocument.Workspace.Contains(externalDocBaseUri + schemasPath + "entity")); + Assert.True(result.OpenApiDocument.Workspace.Contains(externalDocBaseUri + parametersPath + "filter")); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index 7cbd961fc..26afc9720 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,11 +100,6 @@ public void LoadResponseAndSchemaReference() { Schema = new JsonSchemaBuilder() .Ref("#/definitions/SampleObject2") - .Description("Sample description") - .Required("name") - .Properties( - ("name", new JsonSchemaBuilder().Type(SchemaValueType.String)), - ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))) .Build() } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 611f2c3d5..4449072e0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -2,15 +2,12 @@ // 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 @@ -30,15 +27,10 @@ public void ShouldParseProducesInAnyOrder() var result = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "twoResponses.json")); var okSchema = new JsonSchemaBuilder() - .Ref("#/definitions/Item") - .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier."))); + .Ref("#/definitions/Item"); 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))); + .Ref("#/definitions/Error"); var okMediaType = new OpenApiMediaType { @@ -147,12 +139,18 @@ public void ShouldParseProducesInAnyOrder() { Schemas = { - ["Item"] = okSchema, - ["Error"] = errorSchema + ["Item"] = new JsonSchemaBuilder() + .Ref("#/definitions/Item") + .Properties(("id", new JsonSchemaBuilder().Type(SchemaValueType.String).Description("Item identifier."))), + ["Error"] = 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))) } } }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); - } [Fact] @@ -169,10 +167,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))); + .Ref("#/definitions/Error"); var responses = result.OpenApiDocument.Paths["/items"].Operations[OperationType.Get].Responses; foreach (var response in responses) diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index 087220fa7..d4ee7bdf1 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -44,40 +44,37 @@ public static T Clone(T element) where T : IOpenApiSerializable public void ParseDocumentWithWebhooksShouldSucceed() { // Arrange and Act - var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml")); - - var petSchema = new JsonSchemaBuilder() - .Type(SchemaValueType.Object) - .Required("id", "name") - .Properties( - ("id", new JsonSchemaBuilder() - .Type(SchemaValueType.Integer) - .Format("int64")), - ("name", new JsonSchemaBuilder() - .Type(SchemaValueType.String) - ), - ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String)) - ); - - var newPetSchema = new JsonSchemaBuilder() - .Type(SchemaValueType.Object) - .Required("name") - .Properties( - ("id", new JsonSchemaBuilder() - .Type(SchemaValueType.Integer) - .Format("int64")), - ("name", new JsonSchemaBuilder() - .Type(SchemaValueType.String) - ), - ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String)) - ); + var actual = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml")); + var petSchema = new JsonSchemaBuilder().Ref("#/components/schemas/petSchema"); + var newPetSchema = new JsonSchemaBuilder().Ref("#/components/schemas/newPetSchema"); var components = new OpenApiComponents { Schemas = { - ["petSchema"] = petSchema, - ["newPetSchema"] = newPetSchema + ["petSchema"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("id", "name") + .Properties( + ("id", new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64")), + ("name", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String)) + ), + ["newPetSchema"] = new JsonSchemaBuilder() + .Type(SchemaValueType.Object) + .Required("name") + .Properties( + ("id", new JsonSchemaBuilder() + .Type(SchemaValueType.Integer) + .Format("int64")), + ("name", new JsonSchemaBuilder() + .Type(SchemaValueType.String) + ), + ("tag", new JsonSchemaBuilder().Type(SchemaValueType.String))) } }; @@ -213,9 +210,11 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds() } }; + + // Create a clone of the schema to avoid modifying things in components. - var petSchema = components.Schemas["petSchema"]; - var newPetSchema = components.Schemas["newPetSchema"]; + var petSchema = new JsonSchemaBuilder().Ref("#/components/schemas/petSchema"); + var newPetSchema = new JsonSchemaBuilder().Ref("#/components/schemas/newPetSchema"); components.PathItems = new Dictionary { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs index 50cadb81c..b69c5add0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/JsonSchemaTests.cs @@ -239,12 +239,7 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() .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"), + .Ref("#/components/schemas/ErrorModel"), new JsonSchemaBuilder() .Type(SchemaValueType.Object) .Required("rootCause") diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 21d7e2884..e68e25991 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -239,11 +239,11 @@ public void ParseStandardPetStoreDocumentShouldSucceed() ("message", new JsonSchemaBuilder().Type(SchemaValueType.String))) } }; - var petSchema = components.Schemas["pet1"]; + var petSchema = new JsonSchemaBuilder().Ref("#/components/schemas/pet1"); - var newPetSchema = components.Schemas["newPet"]; + var newPetSchema = new JsonSchemaBuilder().Ref("#/components/schemas/newPet"); - var errorModelSchema = components.Schemas["errorModel"]; + var errorModelSchema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel"); var expectedDoc = new OpenApiDocument { @@ -568,11 +568,11 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; - var petSchema = components.Schemas["pet1"]; + var petSchema = new JsonSchemaBuilder().Ref("#/components/schemas/pet1"); - var newPetSchema = components.Schemas["newPet"]; + var newPetSchema = new JsonSchemaBuilder().Ref("#/components/schemas/newPet"); - var errorModelSchema = components.Schemas["errorModel"]; + var errorModelSchema = new JsonSchemaBuilder().Ref("#/components/schemas/errorModel"); var tag1 = new OpenApiTag { @@ -1061,11 +1061,6 @@ public void ParseDocumentWithJsonSchemaReferencesWorks() var expectedSchema = new JsonSchemaBuilder() .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 diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index a9e086061..15068d24b 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -621,10 +621,10 @@ namespace Microsoft.OpenApi.Models 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 SetReferenceHostDocument() { } 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) { } diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs index 68cb9057a..f3afe2ac1 100644 --- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Json.Schema; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -75,41 +74,6 @@ public void OpenApiWorkspacesCanResolveExternalReferences() Assert.Equal("The referenced one", schema.GetDescription()); } - [Fact] - public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short() - { - var doc = new OpenApiDocument(); - var reference = "common#/components/schemas/test"; - doc.CreatePathItem("/", p => - { - p.Description = "Consumer"; - p.CreateOperation(OperationType.Get, op => - op.CreateResponse("200", re => - { - re.Description = "Success"; - re.CreateContent("application/json", co => - co.Schema = new JsonSchemaBuilder().Ref(reference).Build() - ); - }) - ); - }); - - var doc2 = CreateCommonDocument(); - doc.Workspace.RegisterComponents(doc2); - doc2.Workspace.RegisterComponents(doc); - doc.Workspace.AddDocumentId("common", doc2.BaseUri); - var errors = doc.ResolveReferences(); - Assert.Empty(errors); - } - - // Enable Workspace to load from any reader, not just streams. - - // Test fragments - internal void OpenApiWorkspacesShouldLoadDocumentFragments() - { - Assert.True(false); - } - [Fact] public void OpenApiWorkspacesCanResolveReferencesToDocumentFragments() {