Skip to content

Commit 4c26dbb

Browse files
committed
Added hostdocument to OpenApiReference
1 parent a4519b4 commit 4c26dbb

File tree

20 files changed

+287
-151
lines changed

20 files changed

+287
-151
lines changed

src/Microsoft.OpenApi.Hidi/OpenApiService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ string filterByCollection
5353

5454
var result = new OpenApiStreamReader(new OpenApiReaderSettings
5555
{
56-
ReferenceResolution = inlineExternal == true ? ReferenceResolutionSetting.ResolveAllReferences : ReferenceResolutionSetting.ResolveLocalReferences,
56+
LoadExternalRefs = inlineExternal,
5757
RuleSet = ValidationRuleSet.GetDefaultRuleSet(),
5858
BaseUrl = new Uri(inputUrl.AbsoluteUri)
5959
}

src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,10 @@
44
using Microsoft.OpenApi.Any;
55
using Microsoft.OpenApi.Interfaces;
66
using Microsoft.OpenApi.Readers.Interface;
7-
using Microsoft.OpenApi.Readers.ParseNodes;
8-
using Microsoft.OpenApi.Readers.Services;
97
using Microsoft.OpenApi.Validations;
108
using System;
119
using System.Collections.Generic;
1210
using System.IO;
13-
using System.Linq;
14-
using System.Text;
15-
using System.Threading.Tasks;
1611

1712
namespace Microsoft.OpenApi.Readers
1813
{
@@ -30,7 +25,7 @@ public enum ReferenceResolutionSetting
3025
/// </summary>
3126
ResolveLocalReferences,
3227
/// <summary>
33-
/// Not used anymore. Will be removed in v2. Convert all references to references of valid domain objects.
28+
/// ResolveAllReferences effectively means load external references. Will be removed in v2. External references are never "resolved".
3429
/// </summary>
3530
ResolveAllReferences
3631
}
@@ -43,8 +38,14 @@ public class OpenApiReaderSettings
4338
/// <summary>
4439
/// Indicates how references in the source document should be handled.
4540
/// </summary>
41+
/// <remarks>This setting will be going away in the next major version of this library. Use GetEffective on model objects to get resolved references.</remarks>
4642
public ReferenceResolutionSetting ReferenceResolution { get; set; } = ReferenceResolutionSetting.ResolveLocalReferences;
4743

44+
/// <summary>
45+
/// When external references are found, load them into a shared workspace
46+
/// </summary>
47+
public bool LoadExternalRefs { get; set; } = false;
48+
4849
/// <summary>
4950
/// Dictionary of parsers for converting extensions into strongly typed classes
5051
/// </summary>

src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public OpenApiStreamReader(OpenApiReaderSettings settings = null)
2525
{
2626
_settings = settings ?? new OpenApiReaderSettings();
2727

28-
if(_settings.ReferenceResolution == ReferenceResolutionSetting.ResolveAllReferences
28+
if((_settings.ReferenceResolution == ReferenceResolutionSetting.ResolveAllReferences || _settings.LoadExternalRefs)
2929
&& _settings.BaseUrl == null)
3030
{
3131
throw new ArgumentException("BaseUrl must be provided to resolve external references.");

src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic
5353
// Parse the OpenAPI Document
5454
document = context.Parse(input);
5555

56+
if (_settings.LoadExternalRefs)
57+
{
58+
throw new InvalidOperationException("Cannot load external refs using the synchronous Read, use ReadAsync instead.");
59+
}
60+
5661
ResolveReferences(diagnostic, document);
5762
}
5863
catch (OpenApiException ex)
@@ -88,7 +93,12 @@ public async Task<ReadResult> ReadAsync(YamlDocument input)
8893
// Parse the OpenAPI Document
8994
document = context.Parse(input);
9095

91-
await ResolveReferencesAsync(diagnostic, document, _settings.BaseUrl);
96+
if (_settings.LoadExternalRefs)
97+
{
98+
await LoadExternalRefs(document);
99+
}
100+
101+
ResolveReferences(diagnostic, document);
92102
}
93103
catch (OpenApiException ex)
94104
{
@@ -112,52 +122,28 @@ public async Task<ReadResult> ReadAsync(YamlDocument input)
112122
};
113123
}
114124

115-
116-
private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document)
125+
private async Task LoadExternalRefs(OpenApiDocument document)
117126
{
118-
// Resolve References if requested
119-
switch (_settings.ReferenceResolution)
120-
{
121-
case ReferenceResolutionSetting.ResolveAllReferences:
122-
throw new ArgumentException("Cannot resolve all references via a synchronous call. Use ReadAsync.");
123-
case ReferenceResolutionSetting.ResolveLocalReferences:
124-
var errors = document.ResolveReferences(false);
127+
// Create workspace for all documents to live in.
128+
var openApiWorkSpace = new OpenApiWorkspace();
125129

126-
foreach (var item in errors)
127-
{
128-
diagnostic.Errors.Add(item);
129-
}
130-
break;
131-
case ReferenceResolutionSetting.DoNotResolveReferences:
132-
break;
133-
}
130+
// Load this root document into the workspace
131+
var streamLoader = new DefaultStreamLoader(_settings.BaseUrl);
132+
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings);
133+
await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document);
134134
}
135135

136-
private async Task ResolveReferencesAsync(OpenApiDiagnostic diagnostic, OpenApiDocument document, Uri baseUrl)
136+
private void ResolveReferences(OpenApiDiagnostic diagnostic, OpenApiDocument document)
137137
{
138138
List<OpenApiError> errors = new List<OpenApiError>();
139139

140140
// Resolve References if requested
141141
switch (_settings.ReferenceResolution)
142142
{
143143
case ReferenceResolutionSetting.ResolveAllReferences:
144-
145-
// Create workspace for all documents to live in.
146-
var openApiWorkSpace = new OpenApiWorkspace();
147-
148-
// Load this root document into the workspace
149-
var streamLoader = new DefaultStreamLoader(baseUrl);
150-
var workspaceLoader = new OpenApiWorkspaceLoader(openApiWorkSpace, _settings.CustomExternalLoader ?? streamLoader, _settings);
151-
await workspaceLoader.LoadAsync(new OpenApiReference() { ExternalResource = "/" }, document);
152-
153-
// Resolve all references in all the documents loaded into the OpenApiWorkspace
154-
foreach (var doc in openApiWorkSpace.Documents)
155-
{
156-
errors.AddRange(doc.ResolveReferences(true));
157-
}
158-
break;
144+
throw new ArgumentException("Resolving external references is not supported");
159145
case ReferenceResolutionSetting.ResolveLocalReferences:
160-
errors.AddRange(document.ResolveReferences(false));
146+
errors.AddRange(document.ResolveReferences());
161147
break;
162148
case ReferenceResolutionSetting.DoNotResolveReferences:
163149
break;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using Microsoft.OpenApi.Models;
5+
6+
namespace Microsoft.OpenApi.Interfaces
7+
{
8+
/// <summary>
9+
/// OpenApiElements that implement IEffective indicate that their description is not self-contained.
10+
/// External elements affect the effective description.
11+
/// </summary>
12+
/// <remarks>Currently this will only be used for accessing external references.
13+
/// In the next major version, this will be the approach accessing all referenced elements.
14+
/// This will enable us to support merging properties that are peers of the $ref </remarks>
15+
/// <typeparam name="T">Type of OpenApi Element that is being referenced.</typeparam>
16+
public interface IEffective<T> where T : class,IOpenApiElement
17+
{
18+
/// <summary>
19+
/// Returns a calculated and cloned version of the element.
20+
/// </summary>
21+
T GetEffective(OpenApiDocument document);
22+
}
23+
}

src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ public interface IOpenApiReferenceable : IOpenApiSerializable
3131
/// Serialize to OpenAPI V2 document without using reference.
3232
/// </summary>
3333
void SerializeAsV2WithoutReference(IOpenApiWriter writer);
34+
3435
}
3536
}

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -321,27 +321,37 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList<OpenApiServer>
321321
/// <summary>
322322
/// Walk the OpenApiDocument and resolve unresolved references
323323
/// </summary>
324-
/// <param name="useExternal">Indicates if external references should be resolved. Document needs to reference a workspace for this to be possible.</param>
325-
public IEnumerable<OpenApiError> ResolveReferences(bool useExternal = false)
324+
/// <remarks>
325+
/// This method will be replaced by a LoadExternalReferences in the next major update to this library.
326+
/// Resolving references at load time is going to go away.
327+
/// </remarks>
328+
public IEnumerable<OpenApiError> ResolveReferences()
326329
{
327-
var resolver = new OpenApiReferenceResolver(this, useExternal);
330+
var resolver = new OpenApiReferenceResolver(this, false);
328331
var walker = new OpenApiWalker(resolver);
329332
walker.Walk(this);
330333
return resolver.Errors;
331334
}
332335

333-
/// <summary>
334-
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
335-
/// </summary>
336-
public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
336+
/// <summary>
337+
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
338+
/// </summary>
339+
internal T ResolveReferenceTo<T>(OpenApiReference reference) where T : class, IOpenApiReferenceable
340+
{
341+
if (reference.IsExternal)
337342
{
338-
return ResolveReference(reference, false);
343+
return ResolveReference(reference, true) as T;
339344
}
345+
else
346+
{
347+
return ResolveReference(reference, false) as T;
348+
}
349+
}
340350

341-
/// <summary>
342-
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
343-
/// </summary>
344-
public IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool useExternal)
351+
/// <summary>
352+
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
353+
/// </summary>
354+
internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool useExternal)
345355
{
346356
if (reference == null)
347357
{

src/Microsoft.OpenApi/Models/OpenApiParameter.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.OpenApi.Models
1212
/// <summary>
1313
/// Parameter Object.
1414
/// </summary>
15-
public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible
15+
public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IEffective<OpenApiParameter>, IOpenApiExtensible
1616
{
1717
private bool? _explode;
1818

@@ -332,6 +332,18 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer)
332332

333333
writer.WriteEndObject();
334334
}
335+
336+
public OpenApiParameter GetEffective(OpenApiDocument doc)
337+
{
338+
if (this.Reference != null)
339+
{
340+
return doc.ResolveReferenceTo<OpenApiParameter>(this.Reference);
341+
}
342+
else
343+
{
344+
return this;
345+
}
346+
}
335347
}
336348

337349
/// <summary>

src/Microsoft.OpenApi/Models/OpenApiReference.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public class OpenApiReference : IOpenApiSerializable
4545
/// </summary>
4646
public bool IsLocal => ExternalResource == null;
4747

48+
/// <summary>
49+
/// The OpenApiDocument that is hosting the OpenApiReference instance. This is used to enable dereferencing the reference.
50+
/// </summary>
51+
public OpenApiDocument HostDocument { get; set; } = null;
52+
4853
/// <summary>
4954
/// Gets the full reference string for v3.0.
5055
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Microsoft.OpenApi.Models
1111
/// <summary>
1212
/// Schema Object.
1313
/// </summary>
14-
public class OpenApiSchema : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiExtensible
14+
public class OpenApiSchema : IOpenApiSerializable, IOpenApiReferenceable, IEffective<OpenApiSchema>, IOpenApiExtensible
1515
{
1616
/// <summary>
1717
/// Follow JSON Schema definition. Short text providing information about the data.
@@ -252,13 +252,21 @@ public void SerializeAsV3(IOpenApiWriter writer)
252252
}
253253

254254
var settings = writer.GetSettings();
255+
var target = this;
255256

256257
if (Reference != null)
257258
{
258259
if (!settings.ShouldInlineReference(Reference))
259260
{
260261
Reference.SerializeAsV3(writer);
261262
return;
263+
}
264+
else
265+
{
266+
if (Reference.IsExternal) // Temporary until v2
267+
{
268+
target = this.GetEffective(Reference.HostDocument);
269+
}
262270
}
263271

264272
// If Loop is detected then just Serialize as a reference.
@@ -270,7 +278,7 @@ public void SerializeAsV3(IOpenApiWriter writer)
270278
}
271279
}
272280

273-
SerializeAsV3WithoutReference(writer);
281+
target.SerializeAsV3WithoutReference(writer);
274282

275283
if (Reference != null)
276284
{
@@ -283,6 +291,7 @@ public void SerializeAsV3(IOpenApiWriter writer)
283291
/// </summary>
284292
public void SerializeAsV3WithoutReference(IOpenApiWriter writer)
285293
{
294+
286295
writer.WriteStartObject();
287296

288297
// title
@@ -666,5 +675,16 @@ internal void WriteAsSchemaProperties(
666675
// extensions
667676
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0);
668677
}
678+
679+
public OpenApiSchema GetEffective(OpenApiDocument doc)
680+
{
681+
if (this.Reference != null)
682+
{
683+
return doc.ResolveReferenceTo<OpenApiSchema>(this.Reference);
684+
} else
685+
{
686+
return this;
687+
}
688+
}
669689
}
670690
}

0 commit comments

Comments
 (0)