Skip to content

Commit 4e50523

Browse files
Merge pull request #1826 from microsoft/mk/fix-external-ref-resolution
Resolve external document dereference to OpenApiDocument using $ref to $id
2 parents 7261ad9 + a60b992 commit 4e50523

File tree

13 files changed

+194
-108
lines changed

13 files changed

+194
-108
lines changed

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace Microsoft.OpenApi.Models
2424
public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible
2525
{
2626
/// <summary>
27-
/// Related workspace containing OpenApiDocuments that are referenced in this document
27+
/// Related workspace containing components that are referenced in a document
2828
/// </summary>
2929
public OpenApiWorkspace Workspace { get; set; }
3030

src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal async Task<OpenApiDiagnostic> LoadAsync(OpenApiReference reference,
2828
{
2929
_workspace.AddDocumentId(reference.ExternalResource, document.BaseUri);
3030
var version = diagnostic?.SpecificationVersion ?? OpenApiSpecVersion.OpenApi3_0;
31-
_workspace.RegisterComponents(document, version);
31+
_workspace.RegisterComponents(document);
3232
document.Workspace = _workspace;
3333

3434
// Collect remote references by walking document

src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
252252
FixRequestBodyReferences(openApiDoc);
253253

254254
// Register components
255-
openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi2_0);
255+
openApiDoc.Workspace.RegisterComponents(openApiDoc);
256256

257257
return openApiDoc;
258258
}

src/Microsoft.OpenApi/Reader/V3/OpenApiDocumentDeserializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
5454
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc);
5555

5656
// Register components
57-
openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi3_0);
57+
openApiDoc.Workspace.RegisterComponents(openApiDoc);
5858

5959
return openApiDoc;
6060
}

src/Microsoft.OpenApi/Reader/V31/OpenApiDocumentDeserializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
5353
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields, openApiDoc);
5454

5555
// Register components
56-
openApiDoc.Workspace.RegisterComponents(openApiDoc, OpenApiSpecVersion.OpenApi3_1);
56+
openApiDoc.Workspace.RegisterComponents(openApiDoc);
5757

5858
return openApiDoc;
5959
}

src/Microsoft.OpenApi/Services/OpenApiComponentsRegistryExtensions.cs

Lines changed: 0 additions & 97 deletions
This file was deleted.

src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using Microsoft.OpenApi.Extensions;
78
using Microsoft.OpenApi.Interfaces;
89
using Microsoft.OpenApi.Models;
910

@@ -54,6 +55,90 @@ public int ComponentsCount()
5455
return _IOpenApiReferenceableRegistry.Count + _artifactsRegistry.Count;
5556
}
5657

58+
/// <summary>
59+
/// Registers a document's components into the workspace
60+
/// </summary>
61+
/// <param name="document"></param>
62+
public void RegisterComponents(OpenApiDocument document)
63+
{
64+
if (document?.Components == null) return;
65+
66+
string baseUri = document.BaseUri + OpenApiConstants.ComponentsSegment;
67+
string location;
68+
69+
// Register Schema
70+
foreach (var item in document.Components.Schemas)
71+
{
72+
location = item.Value.Id ?? baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key;
73+
74+
RegisterComponent(location, item.Value);
75+
}
76+
77+
// Register Parameters
78+
foreach (var item in document.Components.Parameters)
79+
{
80+
location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key;
81+
RegisterComponent(location, item.Value);
82+
}
83+
84+
// Register Responses
85+
foreach (var item in document.Components.Responses)
86+
{
87+
location = baseUri + ReferenceType.Response.GetDisplayName() + "/" + item.Key;
88+
RegisterComponent(location, item.Value);
89+
}
90+
91+
// Register RequestBodies
92+
foreach (var item in document.Components.RequestBodies)
93+
{
94+
location = baseUri + ReferenceType.RequestBody.GetDisplayName() + "/" + item.Key;
95+
RegisterComponent(location, item.Value);
96+
}
97+
98+
// Register Links
99+
foreach (var item in document.Components.Links)
100+
{
101+
location = baseUri + ReferenceType.Link.GetDisplayName() + "/" + item.Key;
102+
RegisterComponent(location, item.Value);
103+
}
104+
105+
// Register Callbacks
106+
foreach (var item in document.Components.Callbacks)
107+
{
108+
location = baseUri + ReferenceType.Callback.GetDisplayName() + "/" + item.Key;
109+
RegisterComponent(location, item.Value);
110+
}
111+
112+
// Register PathItems
113+
foreach (var item in document.Components.PathItems)
114+
{
115+
location = baseUri + ReferenceType.PathItem.GetDisplayName() + "/" + item.Key;
116+
RegisterComponent(location, item.Value);
117+
}
118+
119+
// Register Examples
120+
foreach (var item in document.Components.Examples)
121+
{
122+
location = baseUri + ReferenceType.Example.GetDisplayName() + "/" + item.Key;
123+
RegisterComponent(location, item.Value);
124+
}
125+
126+
// Register Headers
127+
foreach (var item in document.Components.Headers)
128+
{
129+
location = baseUri + ReferenceType.Header.GetDisplayName() + "/" + item.Key;
130+
RegisterComponent(location, item.Value);
131+
}
132+
133+
// Register SecuritySchemes
134+
foreach (var item in document.Components.SecuritySchemes)
135+
{
136+
location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key;
137+
RegisterComponent(location, item.Value);
138+
}
139+
}
140+
141+
57142
/// <summary>
58143
/// Registers a component in the component registry.
59144
/// </summary>

test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
using System.Collections.Generic;
22
using System.Globalization;
33
using System.IO;
4+
using System.Threading.Tasks;
45
using FluentAssertions;
56
using Microsoft.OpenApi.Extensions;
6-
using Microsoft.OpenApi.Interfaces;
77
using Microsoft.OpenApi.Models;
88
using Microsoft.OpenApi.Models.References;
99
using Microsoft.OpenApi.Reader;
1010
using Microsoft.OpenApi.Tests;
1111
using Microsoft.OpenApi.Writers;
12+
using Microsoft.OpenApi.Services;
1213
using Xunit;
14+
using System.Linq;
1315

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

394396
var outputWriter = new StringWriter(CultureInfo.InvariantCulture);
395-
var writer = new OpenApiJsonWriter(outputWriter, new() { InlineLocalReferences = true } );
397+
var writer = new OpenApiJsonWriter(outputWriter, new() { InlineLocalReferences = true });
396398
actual.OpenApiDocument.SerializeAsV31(writer);
397399
var serialized = outputWriter.ToString();
398400
}
@@ -445,7 +447,7 @@ public void ParseDocumentWithPatternPropertiesInSchemaWorks()
445447
}
446448
}
447449
};
448-
450+
449451
// Serialization
450452
var mediaType = result.OpenApiDocument.Paths["/example"].Operations[OperationType.Get].Responses["200"].Content["application/json"];
451453

@@ -461,7 +463,7 @@ public void ParseDocumentWithPatternPropertiesInSchemaWorks()
461463
type: string
462464
prop3:
463465
type: string";
464-
466+
465467
var actualMediaType = mediaType.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_1);
466468

467469
// Assert
@@ -484,5 +486,49 @@ public void ParseDocumentWithReferenceByIdGetsResolved()
484486
Assert.Equal("object", requestBodySchema.Type);
485487
Assert.Equal("string", parameterSchema.Type);
486488
}
489+
490+
[Fact]
491+
public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWorks()
492+
{
493+
// Arrange
494+
var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath);
495+
496+
var settings = new OpenApiReaderSettings
497+
{
498+
LoadExternalRefs = true,
499+
BaseUrl = new(path),
500+
};
501+
502+
// Act
503+
var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefByJsonPointer.yaml"), settings);
504+
var responseSchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema;
505+
506+
// Assert
507+
result.OpenApiDocument.Workspace.Contains("./externalResource.yaml");
508+
responseSchema.Properties.Count.Should().Be(2); // reference has been resolved
509+
}
510+
511+
[Fact]
512+
public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks()
513+
{
514+
// Arrange
515+
var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath);
516+
517+
var settings = new OpenApiReaderSettings
518+
{
519+
LoadExternalRefs = true,
520+
BaseUrl = new(path),
521+
};
522+
523+
// Act
524+
var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefById.yaml"), settings);
525+
var doc2 = OpenApiDocument.Load(Path.Combine(SampleFolderPath, "externalResource.yaml")).OpenApiDocument;
526+
527+
var requestBodySchema = result.OpenApiDocument.Paths["/resource"].Operations[OperationType.Get].Parameters.First().Schema;
528+
result.OpenApiDocument.Workspace.RegisterComponents(doc2);
529+
530+
// Assert
531+
requestBodySchema.Properties.Count.Should().Be(2); // reference has been resolved
532+
}
487533
}
488534
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
openapi: 3.1.0
2+
info:
3+
title: ReferenceById
4+
version: 1.0.0
5+
paths:
6+
/resource:
7+
get:
8+
parameters:
9+
- name: id
10+
in: query
11+
required: true
12+
schema:
13+
$ref: 'https://example.com/schemas/user.json'
14+
components: {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
openapi: 3.1.0
2+
info:
3+
title: ReferenceById
4+
version: 1.0.0
5+
paths:
6+
/resource:
7+
get:
8+
responses:
9+
'200':
10+
description: OK
11+
content:
12+
application/json:
13+
schema:
14+
$ref: './externalResource.yaml#/components/schemas/todo'
15+
components: {}

0 commit comments

Comments
 (0)