Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -446,14 +446,12 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList<OpenApiServer>

/// <summary>
/// Walks the OpenApiDocument and sets the host document for all IOpenApiReferenceable objects
/// and resolves JsonSchema references
/// </summary>
public IEnumerable<OpenApiError> 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;
}

/// <summary>
Expand Down
13 changes: 1 addition & 12 deletions src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public async Task<ReadResult> ReadAsync(JsonNode jsonNode,
}
}

ResolveReferences(diagnostic, document);
document.SetReferenceHostDocument();
}
catch (OpenApiException ex)
{
Expand Down Expand Up @@ -199,16 +199,5 @@ private async Task<OpenApiDiagnostic> 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<OpenApiError> errors = new();
errors.AddRange(document.ResolveReferences());

foreach (var item in errors)
{
diagnostic.Errors.Add(item);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// 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.
/// </summary>
internal class ReferenceResolver : OpenApiVisitorBase
internal class JsonSchemaReferenceResolver : OpenApiVisitorBase
{
private readonly OpenApiDocument _currentDocument;
private readonly List<OpenApiError> _errors = new();

public ReferenceResolver(OpenApiDocument currentDocument)
public JsonSchemaReferenceResolver(OpenApiDocument currentDocument)
{
_currentDocument = currentDocument;
}
Expand All @@ -31,18 +29,6 @@ public ReferenceResolver(OpenApiDocument currentDocument)
/// </summary>
public IEnumerable<OpenApiError> Errors => _errors;

/// <summary>
/// Visits the referenceable element in the host document
/// </summary>
/// <param name="referenceable">The referenceable element in the doc.</param>
public override void Visit(IOpenApiReferenceable referenceable)
{
if (referenceable.Reference != null)
{
referenceable.Reference.HostDocument = _currentDocument;
}
}

/// <summary>
/// Resolves schemas in components
/// </summary>
Expand Down
33 changes: 33 additions & 0 deletions src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// This class is used to walk an OpenApiDocument and sets the host document of IOpenApiReferenceable objects
/// </summary>
internal class ReferenceHostDocumentSetter : OpenApiVisitorBase
{
private readonly OpenApiDocument _currentDocument;

public ReferenceHostDocumentSetter(OpenApiDocument currentDocument)
{
_currentDocument = currentDocument;
}

/// <summary>
/// Visits the referenceable element in the host document
/// </summary>
/// <param name="referenceable">The referenceable element in the doc.</param>
public override void Visit(IOpenApiReferenceable referenceable)
{
if (referenceable.Reference != null)
{
referenceable.Reference.HostDocument = _currentDocument;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<OpenApiError>
{
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 #/")
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
{
Expand Down Expand Up @@ -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]
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,40 +44,37 @@ public static T Clone<T>(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)))
}
};

Expand Down Expand Up @@ -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<string, OpenApiPathItem>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading