Skip to content

Commit 9a9827a

Browse files
committed
Use unique document ids in URIs in components registry and ref resolution
1 parent 77d1e49 commit 9a9827a

26 files changed

+299
-304
lines changed

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,11 @@ public static class OpenApiConstants
625625
/// </summary>
626626
public const string V2ReferenceUri = "https://registry/definitions/";
627627

628+
/// <summary>
629+
/// The default registry uri for OpenApi documents and workspaces
630+
/// </summary>
631+
public const string BaseRegistryUri = "http://openapi.net/";
632+
628633
#region V2.0
629634

630635
/// <summary>

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8-
using System.Runtime.InteropServices;
98
using System.Security.Cryptography;
109
using System.Text;
1110
using System.Threading;
1211
using System.Threading.Tasks;
1312
using Json.Schema;
13+
using Microsoft.OpenApi.Extensions;
1414
using Microsoft.OpenApi.Interfaces;
1515
using Microsoft.OpenApi.Reader;
1616
using Microsoft.OpenApi.Services;
@@ -97,7 +97,7 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo
9797
public OpenApiDocument()
9898
{
9999
Workspace = new OpenApiWorkspace();
100-
Workspace.AddDocument("/", this);
100+
BaseUri = new(OpenApiConstants.BaseRegistryUri + Guid.NewGuid().ToString());
101101
}
102102

103103
/// <summary>
@@ -488,21 +488,24 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
488488
/// <param name="referenceUri"></param>
489489
/// <returns>A JsonSchema ref.</returns>
490490
public JsonSchema ResolveJsonSchemaReference(Uri referenceUri)
491-
{
492-
if (referenceUri == null) return null;
493-
494-
OpenApiReference reference = new OpenApiReference()
491+
{
492+
string uriLocation;
493+
string id = referenceUri.OriginalString.Split('/')?.Last();
494+
string relativePath = "/components/" + ReferenceType.Schema.GetDisplayName() + "/" + id;
495+
if (referenceUri.OriginalString.StartsWith("#"))
495496
{
496-
ExternalResource = referenceUri.OriginalString,
497-
Id = referenceUri.OriginalString.Split('/').Last(),
498-
Type = ReferenceType.Schema
499-
};
500-
501-
JsonSchema resolvedSchema = reference.ExternalResource.StartsWith("#")
502-
? (JsonSchema)Workspace.ResolveReference<IBaseDocument>(reference.Id, reference.Type, Components) // local ref
503-
: Workspace.ResolveReference<JsonSchema>(reference); // external ref
497+
// Local reference
498+
uriLocation = BaseUri + relativePath;
499+
}
500+
else
501+
{
502+
// External reference
503+
var externalUri = referenceUri.OriginalString.Split('#').First();
504+
var externalDocId = Workspace.GetDocumentId(externalUri);
505+
uriLocation = externalDocId + relativePath;
506+
}
504507

505-
return resolvedSchema;
508+
return (JsonSchema)Workspace.ResolveReference<IBaseDocument>(uriLocation);
506509
}
507510

508511
/// <summary>
@@ -549,16 +552,6 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool
549552
return null;
550553
}
551554

552-
// Todo: Verify if we need to check to see if this external reference is actually targeted at this document.
553-
if (useExternal)
554-
{
555-
if (Workspace == null)
556-
{
557-
throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution);
558-
}
559-
return Workspace.ResolveReference<IOpenApiReferenceable>(reference);
560-
}
561-
562555
if (!reference.Type.HasValue)
563556
{
564557
throw new ArgumentException(Properties.SRResource.LocalReferenceRequiresType);
@@ -579,9 +572,16 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool
579572
return null;
580573
}
581574

582-
return Workspace.ResolveReference<IOpenApiReferenceable>(reference.Id, reference.Type, Components);
583-
}
575+
string uriLocation;
576+
string relativePath = "/components/" + reference.Type.GetDisplayName() + "/" + reference.Id;
577+
578+
uriLocation = useExternal
579+
? Workspace.GetDocumentId(reference.ExternalResource)?.OriginalString + relativePath
580+
: BaseUri + relativePath;
584581

582+
return Workspace.ResolveReference<IOpenApiReferenceable>(uriLocation);
583+
}
584+
585585
/// <summary>
586586
/// Parses a local file path or Url into an Open API document.
587587
/// </summary>

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ namespace Microsoft.OpenApi.Reader.Services
1515
internal class OpenApiRemoteReferenceCollector : OpenApiVisitorBase
1616
{
1717
private readonly Dictionary<string, OpenApiReference> _references = new();
18-
private Guid _guid = new();
1918

2019
/// <summary>
2120
/// List of all internal and external references collected from OpenApiDocument
@@ -34,28 +33,19 @@ public IEnumerable<OpenApiReference> References
3433
/// <param name="referenceable"></param>
3534
public override void Visit(IOpenApiReferenceable referenceable)
3635
{
37-
AddReferences(referenceable.Reference);
36+
AddExternalReferences(referenceable.Reference);
3837
}
3938

4039
/// <summary>
4140
/// Collect internal and external references
4241
/// </summary>
43-
private void AddReferences(OpenApiReference reference)
42+
private void AddExternalReferences(OpenApiReference reference)
4443
{
45-
// External refs
4644
if (reference is {IsExternal: true} &&
4745
!_references.ContainsKey(reference.ExternalResource))
4846
{
4947
_references.Add(reference.ExternalResource, reference);
5048
}
51-
52-
// Local refs
53-
if (reference is { IsExternal: false } &&
54-
!_references.ContainsKey(reference.ReferenceV3))
55-
{
56-
reference.ExternalResource = _guid.ToString();
57-
_references.Add(reference.ReferenceV3, reference);
58-
}
5949
}
6050
}
6151
}

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

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ internal async Task<OpenApiDiagnostic> LoadAsync(OpenApiReference reference,
2626
OpenApiDiagnostic diagnostic = null,
2727
CancellationToken cancellationToken = default)
2828
{
29-
_workspace.AddDocument(reference.ExternalResource, document);
29+
_workspace.AddDocumentId(reference.ExternalResource, document.BaseUri);
30+
_workspace.RegisterComponents(document);
3031
document.Workspace = _workspace;
3132

3233
// Collect remote references by walking document
@@ -43,28 +44,18 @@ internal async Task<OpenApiDiagnostic> LoadAsync(OpenApiReference reference,
4344
// If not already in workspace, load it and process references
4445
if (!_workspace.Contains(item.ExternalResource))
4546
{
46-
if (!Guid.TryParse(item.ExternalResource, out _))
47+
var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute));
48+
var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken);
49+
// Merge diagnostics
50+
if (result.OpenApiDiagnostic != null)
4751
{
48-
var input = await _loader.LoadAsync(new(item.ExternalResource, UriKind.RelativeOrAbsolute));
49-
var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken);
50-
// Merge diagnostics
51-
if (result.OpenApiDiagnostic != null)
52-
{
53-
diagnostic.AppendDiagnostic(result.OpenApiDiagnostic, item.ExternalResource);
54-
}
55-
if (result.OpenApiDocument != null)
56-
{
57-
var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken);
58-
diagnostic = loadDiagnostic;
59-
}
52+
diagnostic.AppendDiagnostic(result.OpenApiDiagnostic, item.ExternalResource);
6053
}
61-
else // local ref in an external file, add this to the documents registry
54+
if (result.OpenApiDocument != null)
6255
{
63-
if (!_workspace.Contains(item.ExternalResource))
64-
{
65-
_workspace.AddDocument(reference.ExternalResource, document);
66-
}
67-
}
56+
var loadDiagnostic = await LoadAsync(item, result.OpenApiDocument, format, diagnostic, cancellationToken);
57+
diagnostic = loadDiagnostic;
58+
}
6859
}
6960
}
7061

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

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -236,41 +236,38 @@ private static string BuildUrl(string scheme, string host, string basePath)
236236

237237
public static OpenApiDocument LoadOpenApi(RootNode rootNode)
238238
{
239-
var openApidoc = new OpenApiDocument();
239+
var openApiDoc = new OpenApiDocument();
240240

241241
var openApiNode = rootNode.GetMap();
242242

243-
ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields);
243+
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields);
244244

245-
if (openApidoc.Paths != null)
245+
if (openApiDoc.Paths != null)
246246
{
247247
ProcessResponsesMediaTypes(
248248
rootNode.GetMap(),
249-
openApidoc.Paths.Values
249+
openApiDoc.Paths.Values
250250
.SelectMany(path => path.Operations?.Values ?? Enumerable.Empty<OpenApiOperation>())
251251
.SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty<OpenApiResponse>()),
252252
openApiNode.Context);
253253
}
254254

255-
ProcessResponsesMediaTypes(rootNode.GetMap(), openApidoc.Components?.Responses?.Values, openApiNode.Context);
255+
ProcessResponsesMediaTypes(rootNode.GetMap(), openApiDoc.Components?.Responses?.Values, openApiNode.Context);
256256

257257
// Post Process OpenApi Object
258-
if (openApidoc.Servers == null)
258+
if (openApiDoc.Servers == null)
259259
{
260-
openApidoc.Servers = new List<OpenApiServer>();
260+
openApiDoc.Servers = new List<OpenApiServer>();
261261
}
262262

263-
MakeServers(openApidoc.Servers, openApiNode.Context, rootNode);
263+
MakeServers(openApiDoc.Servers, openApiNode.Context, rootNode);
264264

265-
FixRequestBodyReferences(openApidoc);
265+
FixRequestBodyReferences(openApiDoc);
266266

267267
// Register components
268-
//if (openApidoc.Components != null)
269-
//{
270-
// openApidoc.Workspace.RegisterComponents(openApidoc.BaseUri, openApidoc.Components);
271-
//}
268+
openApiDoc.Workspace.RegisterComponents(openApiDoc);
272269

273-
return openApidoc;
270+
return openApiDoc;
274271
}
275272

276273
private static void ProcessResponsesMediaTypes(MapNode mapNode, IEnumerable<OpenApiResponse> responses, ParsingContext context)

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.OpenApi.Extensions;
55
using Microsoft.OpenApi.Models;
66
using Microsoft.OpenApi.Reader.ParseNodes;
7+
using Microsoft.OpenApi.Services;
78

89
namespace Microsoft.OpenApi.Reader.V3
910
{
@@ -46,17 +47,15 @@ internal static partial class OpenApiV3Deserializer
4647

4748
public static OpenApiDocument LoadOpenApi(RootNode rootNode)
4849
{
49-
var openApidoc = new OpenApiDocument();
50+
var openApiDoc = new OpenApiDocument();
5051
var openApiNode = rootNode.GetMap();
5152

52-
ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields);
53+
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields);
5354

54-
//if (openApidoc.Components != null)
55-
//{
56-
// openApidoc.Workspace.RegisterComponents(openApidoc);
57-
//}
55+
// Register components
56+
openApiDoc.Workspace.RegisterComponents(openApiDoc);
5857

59-
return openApidoc;
58+
return openApiDoc;
6059
}
6160
}
6261
}

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
using Microsoft.OpenApi.Extensions;
1+
using System;
2+
using Microsoft.OpenApi.Extensions;
23
using Microsoft.OpenApi.Models;
34
using Microsoft.OpenApi.Reader.ParseNodes;
5+
using Microsoft.OpenApi.Services;
46

57
namespace Microsoft.OpenApi.Reader.V31
68
{
@@ -45,12 +47,15 @@ internal static partial class OpenApiV31Deserializer
4547

4648
public static OpenApiDocument LoadOpenApi(RootNode rootNode)
4749
{
48-
var openApidoc = new OpenApiDocument();
50+
var openApiDoc = new OpenApiDocument();
4951
var openApiNode = rootNode.GetMap();
5052

51-
ParseMap(openApiNode, openApidoc, _openApiFixedFields, _openApiPatternFields);
53+
ParseMap(openApiNode, openApiDoc, _openApiFixedFields, _openApiPatternFields);
5254

53-
return openApidoc;
55+
// Register components
56+
openApiDoc.Workspace.RegisterComponents(openApiDoc);
57+
58+
return openApiDoc;
5459
}
5560
}
5661
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using Microsoft.OpenApi.Extensions;
5+
using Microsoft.OpenApi.Models;
6+
7+
namespace Microsoft.OpenApi.Services
8+
{
9+
internal static class OpenApiComponentsRegistryExtensions
10+
{
11+
public static void RegisterComponents(this OpenApiWorkspace workspace, OpenApiDocument document)
12+
{
13+
if (document?.Components == null) return;
14+
15+
var baseUri = document.BaseUri + "/components/";
16+
17+
// Register Schema
18+
foreach (var item in document.Components.Schemas)
19+
{
20+
var location = baseUri + ReferenceType.Schema.GetDisplayName() + "/" + item.Key;
21+
workspace.RegisterComponent(location, item.Value);
22+
}
23+
24+
// Register Parameters
25+
foreach (var item in document.Components.Parameters)
26+
{
27+
var location = baseUri + ReferenceType.Parameter.GetDisplayName() + "/" + item.Key;
28+
workspace.RegisterComponent(location, item.Value);
29+
}
30+
31+
// Register Responses
32+
foreach (var item in document.Components.Responses)
33+
{
34+
var location = baseUri + ReferenceType.Response.GetDisplayName() + "/" + item.Key;
35+
workspace.RegisterComponent(location, item.Value);
36+
}
37+
38+
// Register RequestBodies
39+
foreach (var item in document.Components.RequestBodies)
40+
{
41+
var location = baseUri + ReferenceType.RequestBody.GetDisplayName() + "/" + item.Key;
42+
workspace.RegisterComponent(location, item.Value);
43+
}
44+
45+
// Register Links
46+
foreach (var item in document.Components.Links)
47+
{
48+
var location = baseUri + ReferenceType.Link.GetDisplayName() + "/" + item.Key;
49+
workspace.RegisterComponent(location, item.Value);
50+
}
51+
52+
// Register Callbacks
53+
foreach (var item in document.Components.Callbacks)
54+
{
55+
var location = baseUri + ReferenceType.Callback.GetDisplayName() + "/" + item.Key;
56+
workspace.RegisterComponent(location, item.Value);
57+
}
58+
59+
// Register PathItems
60+
foreach (var item in document.Components.PathItems)
61+
{
62+
var location = baseUri + ReferenceType.PathItem.GetDisplayName() + "/" + item.Key;
63+
workspace.RegisterComponent(location, item.Value);
64+
}
65+
66+
// Register Examples
67+
foreach (var item in document.Components.Examples)
68+
{
69+
var location = baseUri + ReferenceType.Example.GetDisplayName() + "/" + item.Key;
70+
workspace.RegisterComponent(location, item.Value);
71+
}
72+
73+
// Register Headers
74+
foreach (var item in document.Components.Headers)
75+
{
76+
var location = baseUri + ReferenceType.Header.GetDisplayName() + "/" + item.Key;
77+
workspace.RegisterComponent(location, item.Value);
78+
}
79+
80+
// Register SecuritySchemes
81+
foreach (var item in document.Components.SecuritySchemes)
82+
{
83+
var location = baseUri + ReferenceType.SecurityScheme.GetDisplayName() + "/" + item.Key;
84+
workspace.RegisterComponent(location, item.Value);
85+
}
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)