Skip to content

Commit 5090c01

Browse files
committed
Support loading an OpenApiDocument with remote references.
1 parent 6b4e2ce commit 5090c01

File tree

17 files changed

+177
-76
lines changed

17 files changed

+177
-76
lines changed

src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public async Task<ReadResult> ReadAsync(Stream input)
5757
// YamlDocument doesn't support async reading.
5858
bufferedStream = new MemoryStream();
5959
await input.CopyToAsync(bufferedStream);
60+
bufferedStream.Position = 0;
6061
}
6162

6263
var reader = new StreamReader(bufferedStream);

src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ public OpenApiDocument Read(TextReader input, out OpenApiDiagnostic diagnostic)
5353
return new OpenApiYamlDocumentReader(this._settings).Read(yamlDocument, out diagnostic);
5454
}
5555

56+
/// <summary>
57+
/// Reads the content of the TextReader. If there are references to external documents then they will be read asynchronously.
58+
/// </summary>
59+
/// <param name="input">TextReader containing OpenAPI description to parse.</param>
60+
/// <returns>A ReadResult instance that contains the resulting OpenApiDocument and a diagnostics instance.</returns>
5661
public async Task<ReadResult> ReadAsync(TextReader input)
5762
{
5863
YamlDocument yamlDocument;

src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.IO;
67
using System.Threading.Tasks;
78
using Microsoft.OpenApi.Exceptions;
@@ -134,28 +135,38 @@ private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument doc
134135

135136
private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document)
136137
{
138+
List<OpenApiError> errors = new List<OpenApiError>();
139+
137140
// Resolve References if requested
138141
switch (_settings.ReferenceResolution)
139142
{
140143
case ReferenceResolutionSetting.ResolveAllReferences:
144+
145+
// Create workspace for all documents to live in.
141146
var openApiWorkSpace = new OpenApiWorkspace();
142-
document.Workspace = openApiWorkSpace;
143-
var streamLoader = new DefaultStreamLoader();
144147

148+
// Load this root document into the workspace
149+
var streamLoader = new DefaultStreamLoader();
145150
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings);
146151
await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document);
147-
break;
148-
case ReferenceResolutionSetting.ResolveLocalReferences:
149-
var errors = document.ResolveReferences(false);
150152

151-
foreach (var item in errors)
153+
// Resolve all references in all the documents loaded into the OpenApiWorkspace
154+
foreach (var doc in openApiWorkSpace.Documents)
152155
{
153-
diagnostic.Errors.Add(item);
156+
errors.AddRange(doc.ResolveReferences(true));
154157
}
155158
break;
159+
case ReferenceResolutionSetting.ResolveLocalReferences:
160+
errors.AddRange(document.ResolveReferences(false));
161+
break;
156162
case ReferenceResolutionSetting.DoNotResolveReferences:
157163
break;
158164
}
165+
166+
foreach (var item in errors)
167+
{
168+
diagnostic.Errors.Add(item);
169+
}
159170
}
160171

161172

src/Microsoft.OpenApi.Readers/Services/OpenApiRemoteReferenceCollector.cs

Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license.
33

44
using System.Collections.Generic;
5+
using Microsoft.OpenApi.Interfaces;
56
using Microsoft.OpenApi.Models;
67
using Microsoft.OpenApi.Services;
78

@@ -19,9 +20,6 @@ public OpenApiRemoteReferenceCollector(OpenApiDocument document)
1920
_document = document;
2021
}
2122

22-
// TODO PathItem
23-
// TODO Example
24-
2523
/// <summary>
2624
/// List of external references collected from OpenApiDocument
2725
/// </summary>
@@ -33,60 +31,12 @@ public IEnumerable<OpenApiReference> References
3331
}
3432

3533
/// <summary>
36-
/// Collect external reference
37-
/// </summary>
38-
public override void Visit(OpenApiParameter parameter)
39-
{
40-
AddReference(parameter.Reference);
41-
}
42-
43-
/// <summary>
44-
/// Collect external reference
45-
/// </summary>
46-
public override void Visit(OpenApiCallback callback)
47-
{
48-
AddReference(callback.Reference);
49-
50-
}
51-
52-
/// <summary>
53-
/// Collect external reference
54-
/// </summary>
55-
public override void Visit(OpenApiLink link)
56-
{
57-
AddReference(link.Reference);
58-
}
59-
60-
/// <summary>
61-
/// Collect external reference
62-
/// </summary>
63-
public override void Visit(OpenApiRequestBody requestBody)
64-
{
65-
AddReference(requestBody.Reference);
66-
}
67-
68-
/// <summary>
69-
/// Collect external reference
70-
/// </summary>
71-
public override void Visit(OpenApiResponse header)
72-
{
73-
AddReference(header.Reference);
74-
}
75-
76-
/// <summary>
77-
/// Collect external reference
78-
/// </summary>
79-
public override void Visit(OpenApiHeader header)
80-
{
81-
AddReference(header.Reference);
82-
}
83-
84-
/// <summary>
85-
/// Collect external reference
34+
/// Collect reference for each reference
8635
/// </summary>
87-
public override void Visit(OpenApiSchema schema)
36+
/// <param name="referenceable"></param>
37+
public override void Visit(IOpenApiReferenceable referenceable)
8838
{
89-
AddReference(schema.Reference);
39+
AddReference(referenceable.Reference);
9040
}
9141

9242
/// <summary>
@@ -104,7 +54,6 @@ private void AddReference(OpenApiReference reference)
10454
}
10555
}
10656
}
107-
}
108-
57+
}
10958
}
11059
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ internal async Task LoadAsync(OpenApiReference reference, OpenApiDocument docume
4343
// If not already in workspace, load it and process references
4444
if (!_workspace.Contains(item.ExternalResource))
4545
{
46-
var input = _loader.Load(new Uri(item.ExternalResource));
46+
var input = await _loader.LoadAsync(new Uri(item.ExternalResource, UriKind.RelativeOrAbsolute));
4747
var result = await reader.ReadAsync(input); // TODO merge _diagnositics
4848
await LoadAsync(item, result.OpenApiDocument);
4949
}

src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ public OpenApiReference ConvertToOpenApiReference(string reference, ReferenceTyp
161161
return new OpenApiReference
162162
{
163163
ExternalResource = segments[0],
164+
Type = type,
164165
Id = segments[1].Substring(1)
165166
};
166167
}

src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public OpenApiReference ConvertToOpenApiReference(
6969
// "$ref": "Pet.json"
7070
return new OpenApiReference
7171
{
72+
Type = type,
7273
ExternalResource = segments[0]
7374
};
7475
}
@@ -89,12 +90,18 @@ public OpenApiReference ConvertToOpenApiReference(
8990
// "$ref": "#/components/schemas/Pet"
9091
return ParseLocalReference(segments[1]);
9192
}
92-
93+
// Where fragments point into a non-OpenAPI document, the id will be the complete fragment identifier
94+
string id = segments[1];
9395
// $ref: externalSource.yaml#/Pet
96+
if (id.StartsWith("/components/"))
97+
{
98+
id = segments[1].Split('/')[3];
99+
}
94100
return new OpenApiReference
95101
{
96102
ExternalResource = segments[0],
97-
Id = segments[1].Substring(1)
103+
Type = type,
104+
Id = id
98105
};
99106
}
100107
}

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,11 +348,15 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool u
348348
return null;
349349
}
350350

351-
if (reference.IsExternal && !useExternal)
351+
// Todo: Verify if we need to check to see if this external reference is actually targeted at this document.
352+
if (useExternal)
352353
{
353-
// Should not attempt to resolve external references against a single document.
354-
throw new ArgumentException(Properties.SRResource.RemoteReferenceNotSupported);
355-
}
354+
if (this.Workspace == null)
355+
{
356+
throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution);
357+
}
358+
return this.Workspace.ResolveReference(reference);
359+
}
356360

357361
if (!reference.Type.HasValue)
358362
{

src/Microsoft.OpenApi/Properties/SRResource.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.OpenApi/Properties/SRResource.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,7 @@
216216
<data name="Validation_StringMustBeEmailAddress" xml:space="preserve">
217217
<value>The string '{0}' MUST be in the format of an email address.</value>
218218
</data>
219+
<data name="WorkspaceRequredForExternalReferenceResolution" xml:space="preserve">
220+
<value>OpenAPI document must be added to an OpenApiWorkspace to be able to resolve external references.</value>
221+
</data>
219222
</root>

0 commit comments

Comments
 (0)