Skip to content

Commit

Permalink
Merge pull request #1826 from microsoft/mk/fix-external-ref-resolution
Browse files Browse the repository at this point in the history
Resolve external document dereference to OpenApiDocument using $ref to $id
  • Loading branch information
MaggieKimani1 authored Oct 3, 2024
2 parents 7261ad9 + a60b992 commit 4e50523
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 108 deletions.
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace Microsoft.OpenApi.Models
public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible
{
/// <summary>
/// Related workspace containing OpenApiDocuments that are referenced in this document
/// Related workspace containing components that are referenced in a document
/// </summary>
public OpenApiWorkspace Workspace { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal async Task<OpenApiDiagnostic> LoadAsync(OpenApiReference reference,
{
_workspace.AddDocumentId(reference.ExternalResource, document.BaseUri);
var version = diagnostic?.SpecificationVersion ?? OpenApiSpecVersion.OpenApi3_0;
_workspace.RegisterComponents(document, version);
_workspace.RegisterComponents(document);
document.Workspace = _workspace;

// Collect remote references by walking document
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
FixRequestBodyReferences(openApiDoc);

// Register components
openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi2_0);
openApiDoc.Workspace.RegisterComponents(openApiDoc);

return openApiDoc;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc);

// Register components
openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi3_0);
openApiDoc.Workspace.RegisterComponents(openApiDoc);

return openApiDoc;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc);

// Register components
openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi3_1);
openApiDoc.Workspace.RegisterComponents(openApiDoc);

return openApiDoc;
}
Expand Down

This file was deleted.

85 changes: 85 additions & 0 deletions src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;

Expand Down Expand Up @@ -54,6 +55,90 @@ public int ComponentsCount()
return _IOpenApiReferenceableRegistry.Count + _artifactsRegistry.Count;
}

/// <summary>
/// Registers a document's components into the workspace
/// </summary>
/// <param name="document"></param>
public void RegisterComponents(OpenApiDocument document)
{
if (document?.Components == null) return;

string baseUri = document.BaseUri + OpenApiConstants.ComponentsSegment;
string location;

// Register Schema
foreach (var item in document.Components.Schemas)
{
location = item.Value.Id ?? baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key;

RegisterComponent(location, item.Value);
}

// Register Parameters
foreach (var item in document.Components.Parameters)
{
location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}

// Register Responses
foreach (var item in document.Components.Responses)
{
location = baseUri + ReferenceType.Response.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}

// Register RequestBodies
foreach (var item in document.Components.RequestBodies)
{
location = baseUri + ReferenceType.RequestBody.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}

// Register Links
foreach (var item in document.Components.Links)
{
location = baseUri + ReferenceType.Link.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}

// Register Callbacks
foreach (var item in document.Components.Callbacks)
{
location = baseUri + ReferenceType.Callback.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}

// Register PathItems
foreach (var item in document.Components.PathItems)
{
location = baseUri + ReferenceType.PathItem.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}

// Register Examples
foreach (var item in document.Components.Examples)
{
location = baseUri + ReferenceType.Example.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}

// Register Headers
foreach (var item in document.Components.Headers)
{
location = baseUri + ReferenceType.Header.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}

// Register SecuritySchemes
foreach (var item in document.Components.SecuritySchemes)
{
location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key;
RegisterComponent(location, item.Value);
}
}


/// <summary>
/// Registers a component in the component registry.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.References;
using Microsoft.OpenApi.Reader;
using Microsoft.OpenApi.Tests;
using Microsoft.OpenApi.Writers;
using Microsoft.OpenApi.Services;
using Xunit;
using System.Linq;

namespace Microsoft.OpenApi.Readers.Tests.V31Tests
{
Expand Down Expand Up @@ -392,7 +394,7 @@ public void ParseDocumentsWithReusablePathItemInWebhooksSucceeds()
new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1 });

var outputWriter = new StringWriter(CultureInfo.InvariantCulture);
var writer = new OpenApiJsonWriter(outputWriter, new() { InlineLocalReferences = true } );
var writer = new OpenApiJsonWriter(outputWriter, new() { InlineLocalReferences = true });
actual.OpenApiDocument.SerializeAsV31(writer);
var serialized = outputWriter.ToString();
}
Expand Down Expand Up @@ -445,7 +447,7 @@ public void ParseDocumentWithPatternPropertiesInSchemaWorks()
}
}
};

// Serialization
var mediaType = result.OpenApiDocument.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content["application/json"];

Expand All @@ -461,7 +463,7 @@ public void ParseDocumentWithPatternPropertiesInSchemaWorks()
type: string
prop3:
type: string";

var actualMediaType = mediaType.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1);

// Assert
Expand All @@ -484,5 +486,49 @@ public void ParseDocumentWithReferenceByIdGetsResolved()
Assert.Equal("object", requestBodySchema.Type);
Assert.Equal("string", parameterSchema.Type);
}

[Fact]
public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWorks()
{
// Arrange
var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath);

var settings = new OpenApiReaderSettings
{
LoadExternalRefs = true,
BaseUrl = new(path),
};

// Act
var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefByJsonPointer.yaml"), settings);
var responseSchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema;

// Assert
result.OpenApiDocument.Workspace.Contains("./externalResource.yaml");
responseSchema.Properties.Count.Should().Be(2); // reference has been resolved
}

[Fact]
public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks()
{
// Arrange
var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath);

var settings = new OpenApiReaderSettings
{
LoadExternalRefs = true,
BaseUrl = new(path),
};

// Act
var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefById.yaml"), settings);
var doc2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "externalResource.yaml")).OpenApiDocument;

var requestBodySchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Parameters.First().Schema;
result.OpenApiDocument.Workspace.RegisterComponents(doc2);

// Assert
requestBodySchema.Properties.Count.Should().Be(2); // reference has been resolved
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
openapi: 3.1.0
info:
title: ReferenceById
version: 1.0.0
paths:
/resource:
get:
parameters:
- name: id
in: query
required: true
schema:
$ref: 'https://example.com/schemas/user.json'
components: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
openapi: 3.1.0
info:
title: ReferenceById
version: 1.0.0
paths:
/resource:
get:
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: './externalResource.yaml#/components/schemas/todo'
components: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
openapi: 3.1.0
info:
title: ReferencedById
version: 1.0.0
paths: {}
components:
schemas:
todo:
type: object
properties:
id:
type: string
name:
type: string
user:
$id: 'https://example.com/schemas/user.json'
type: object
properties:
id:
type: string
name:
type: string
Loading

0 comments on commit 4e50523

Please sign in to comment.