Skip to content

Commit 33236ad

Browse files
committed
Merge method resolving JsonSchemas with SetHostDocument
1 parent f6912ab commit 33236ad

File tree

5 files changed

+217
-56
lines changed

5 files changed

+217
-56
lines changed

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -445,28 +445,15 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList<OpenApiServer>
445445
}
446446

447447
/// <summary>
448-
/// Walk the OpenApiDocument and resolve unresolved references
448+
/// Walks the OpenApiDocument and sets the host document for all IOpenApiReferenceable objects
449+
/// and resolves JsonSchema references
449450
/// </summary>
450-
/// <remarks>
451-
/// This method will be replaced by a LoadExternalReferences in the next major update to this library.
452-
/// Resolving references at load time is going to go away.
453-
/// </remarks>
454-
public IEnumerable<OpenApiError> ResolveJsonSchemaReferences()
451+
public IEnumerable<OpenApiError> ResolveReferences()
455452
{
456-
var jsonSchemaResolver = new JsonSchemaReferenceResolver(this);
457-
var walker = new OpenApiWalker(jsonSchemaResolver);
458-
walker.Walk(this);
459-
return jsonSchemaResolver.Errors;
460-
}
461-
462-
/// <summary>
463-
/// Walks the OpenApiDocument and sets the host document for all referenceable objects
464-
/// </summary>
465-
public void SetHostDocument()
466-
{
467-
var resolver = new HostDocumentResolver(this);
453+
var resolver = new ReferenceResolver(this);
468454
var walker = new OpenApiWalker(resolver);
469455
walker.Walk(this);
456+
return resolver.Errors;
470457
}
471458

472459
/// <summary>
@@ -502,6 +489,7 @@ public JsonSchema ResolveJsonSchemaReference(Uri referenceUri)
502489
string uriLocation;
503490
string id = referenceUri.OriginalString.Split('/')?.Last();
504491
string relativePath = "/components/" + ReferenceType.Schema.GetDisplayName() + "/" + id;
492+
505493
if (referenceUri.OriginalString.StartsWith("#"))
506494
{
507495
// Local reference

src/Microsoft.OpenApi/Reader/OpenApiJsonReader.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ public async Task<ReadResult> ReadAsync(JsonNode jsonNode,
9595
}
9696
}
9797

98-
SetHostDocument(document);
9998
ResolveReferences(diagnostic, document);
10099
}
101100
catch (OpenApiException ex)
@@ -201,15 +200,10 @@ private async Task<OpenApiDiagnostic> LoadExternalRefs(OpenApiDocument document,
201200
return await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document, format ?? OpenApiConstants.Json, null, cancellationToken);
202201
}
203202

204-
private void SetHostDocument(OpenApiDocument document)
205-
{
206-
document.SetHostDocument();
207-
}
208-
209203
private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document)
210204
{
211205
List<OpenApiError> errors = new();
212-
errors.AddRange(document.ResolveJsonSchemaReferences());
206+
errors.AddRange(document.ResolveReferences());
213207

214208
foreach (var item in errors)
215209
{

src/Microsoft.OpenApi/Services/HostDocumentResolver.cs

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Json.Schema;
7+
using Microsoft.OpenApi.Exceptions;
8+
using System.Linq;
9+
using Microsoft.OpenApi.Interfaces;
10+
using Microsoft.OpenApi.Models;
11+
using Microsoft.OpenApi.Extensions;
12+
13+
namespace Microsoft.OpenApi.Services
14+
{
15+
internal class ReferenceResolver : OpenApiVisitorBase
16+
{
17+
private readonly OpenApiDocument _currentDocument;
18+
private readonly List<OpenApiError> _errors = new();
19+
20+
public ReferenceResolver(OpenApiDocument currentDocument)
21+
{
22+
_currentDocument = currentDocument;
23+
}
24+
25+
/// <summary>
26+
/// List of errors related to the OpenApiDocument
27+
/// </summary>
28+
public IEnumerable<OpenApiError> Errors => _errors;
29+
30+
/// <summary>
31+
/// Visits the referenceable element in the host document
32+
/// </summary>
33+
/// <param name="referenceable">The referenceable element in the doc.</param>
34+
public override void Visit(IOpenApiReferenceable referenceable)
35+
{
36+
if (referenceable.Reference != null)
37+
{
38+
referenceable.Reference.HostDocument = _currentDocument;
39+
}
40+
}
41+
42+
/// <summary>
43+
/// Resolves schemas in components
44+
/// </summary>
45+
/// <param name="components"></param>
46+
public override void Visit(OpenApiComponents components)
47+
{
48+
components.Schemas = ResolveJsonSchemas(components.Schemas);
49+
}
50+
51+
/// <summary>
52+
/// Resolve all JsonSchema references used in mediaType object
53+
/// </summary>
54+
/// <param name="mediaType"></param>
55+
public override void Visit(OpenApiMediaType mediaType)
56+
{
57+
ResolveJsonSchema(mediaType.Schema, r => mediaType.Schema = r ?? mediaType.Schema);
58+
}
59+
60+
/// <summary>
61+
/// Resolve all JsonSchema references used in a parameter
62+
/// </summary>
63+
public override void Visit(OpenApiParameter parameter)
64+
{
65+
ResolveJsonSchema(parameter.Schema, r => parameter.Schema = r);
66+
}
67+
68+
/// <summary>
69+
/// Resolve all references used in a JsonSchema
70+
/// </summary>
71+
/// <param name="schema"></param>
72+
public override void Visit(ref JsonSchema schema)
73+
{
74+
var reference = schema.GetRef();
75+
var description = schema.GetDescription();
76+
var summary = schema.GetSummary();
77+
78+
if (schema.Keywords.Count.Equals(1) && reference != null)
79+
{
80+
schema = ResolveJsonSchemaReference(reference, description, summary);
81+
}
82+
83+
var builder = new JsonSchemaBuilder();
84+
if (schema?.Keywords is { } keywords)
85+
{
86+
foreach (var keyword in keywords)
87+
{
88+
builder.Add(keyword);
89+
}
90+
}
91+
92+
ResolveJsonSchema(schema.GetItems(), r => builder.Items(r));
93+
ResolveJsonSchemaList((IList<JsonSchema>)schema.GetOneOf(), r => builder.OneOf(r));
94+
ResolveJsonSchemaList((IList<JsonSchema>)schema.GetAllOf(), r => builder.AllOf(r));
95+
ResolveJsonSchemaList((IList<JsonSchema>)schema.GetAnyOf(), r => builder.AnyOf(r));
96+
ResolveJsonSchemaMap((IDictionary<string, JsonSchema>)schema.GetProperties(), r => builder.Properties((IReadOnlyDictionary<string, JsonSchema>)r));
97+
ResolveJsonSchema(schema.GetAdditionalProperties(), r => builder.AdditionalProperties(r));
98+
99+
schema = builder.Build();
100+
}
101+
102+
/// <summary>
103+
/// Visits an IBaseDocument instance
104+
/// </summary>
105+
/// <param name="document"></param>
106+
public override void Visit(IBaseDocument document) { }
107+
108+
private Dictionary<string, JsonSchema> ResolveJsonSchemas(IDictionary<string, JsonSchema> schemas)
109+
{
110+
var resolvedSchemas = new Dictionary<string, JsonSchema>();
111+
foreach (var schema in schemas)
112+
{
113+
var schemaValue = schema.Value;
114+
Visit(ref schemaValue);
115+
resolvedSchemas[schema.Key] = schemaValue;
116+
}
117+
118+
return resolvedSchemas;
119+
}
120+
121+
/// <summary>
122+
/// Resolves the target to a JSON schema reference by retrieval from Schema registry
123+
/// </summary>
124+
/// <param name="reference">The JSON schema reference.</param>
125+
/// <param name="description">The schema's description.</param>
126+
/// <param name="summary">The schema's summary.</param>
127+
/// <returns></returns>
128+
public JsonSchema ResolveJsonSchemaReference(Uri reference, string description = null, string summary = null)
129+
{
130+
var resolvedSchema = _currentDocument.ResolveJsonSchemaReference(reference);
131+
132+
if (resolvedSchema != null)
133+
{
134+
var resolvedSchemaBuilder = new JsonSchemaBuilder();
135+
136+
foreach (var keyword in resolvedSchema.Keywords)
137+
{
138+
resolvedSchemaBuilder.Add(keyword);
139+
140+
// Replace the resolved schema's description with that of the schema reference
141+
if (!string.IsNullOrEmpty(description))
142+
{
143+
resolvedSchemaBuilder.Description(description);
144+
}
145+
146+
// Replace the resolved schema's summary with that of the schema reference
147+
if (!string.IsNullOrEmpty(summary))
148+
{
149+
resolvedSchemaBuilder.Summary(summary);
150+
}
151+
}
152+
153+
return resolvedSchemaBuilder.Build();
154+
}
155+
else
156+
{
157+
var referenceId = reference.OriginalString.Split('/').LastOrDefault();
158+
throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, referenceId));
159+
}
160+
}
161+
162+
private void ResolveJsonSchema(JsonSchema schema, Action<JsonSchema> assign)
163+
{
164+
if (schema == null) return;
165+
var reference = schema.GetRef();
166+
var description = schema.GetDescription();
167+
var summary = schema.GetSummary();
168+
169+
if (reference != null)
170+
{
171+
assign(ResolveJsonSchemaReference(reference, description, summary));
172+
}
173+
}
174+
175+
private void ResolveJsonSchemaList(IList<JsonSchema> list, Action<List<JsonSchema>> assign)
176+
{
177+
if (list == null) return;
178+
179+
for (int i = 0; i < list.Count; i++)
180+
{
181+
var entity = list[i];
182+
var reference = entity?.GetRef();
183+
if (reference != null)
184+
{
185+
list[i] = ResolveJsonSchemaReference(reference);
186+
}
187+
}
188+
189+
assign(list.ToList());
190+
}
191+
192+
private void ResolveJsonSchemaMap(IDictionary<string, JsonSchema> map, Action<IDictionary<string, JsonSchema>> assign)
193+
{
194+
if (map == null) return;
195+
196+
foreach (var key in map.Keys.ToList())
197+
{
198+
var entity = map[key];
199+
var reference = entity.GetRef();
200+
if (reference != null)
201+
{
202+
map[key] = ResolveJsonSchemaReference(reference);
203+
}
204+
}
205+
206+
assign(map.ToDictionary(e => e.Key, e => e.Value));
207+
}
208+
}
209+
}

test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public void OpenApiWorkspacesAllowDocumentsToReferenceEachOther_short()
9898
doc.Workspace.RegisterComponents(doc2);
9999
doc2.Workspace.RegisterComponents(doc);
100100
doc.Workspace.AddDocumentId("common", doc2.BaseUri);
101-
var errors = doc.ResolveJsonSchemaReferences();
101+
var errors = doc.ResolveReferences();
102102
Assert.Empty(errors);
103103
}
104104

0 commit comments

Comments
 (0)