Skip to content

Commit 77cd25a

Browse files
authored
Merge pull request #1609 from microsoft/is/component-registry
Creates component registries in the `OpenApiWorkspace` for `$ref` resolution
2 parents 1e57b38 + 11b3399 commit 77cd25a

File tree

71 files changed

+1135
-1201
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1135
-1201
lines changed

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

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

633+
/// <summary>
634+
/// The default registry uri for OpenApi documents and workspaces
635+
/// </summary>
636+
public const string BaseRegistryUri = "https://openapi.net/";
637+
633638
#region V2.0
634639

635640
/// <summary>

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 44 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
using System.Threading;
1111
using System.Threading.Tasks;
1212
using Json.Schema;
13-
using Microsoft.OpenApi.Exceptions;
13+
using Microsoft.OpenApi.Extensions;
1414
using Microsoft.OpenApi.Interfaces;
1515
using Microsoft.OpenApi.Reader;
1616
using Microsoft.OpenApi.Services;
@@ -94,8 +94,12 @@ public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IBaseDo
9494
/// <summary>
9595
/// Parameter-less constructor
9696
/// </summary>
97-
public OpenApiDocument() { }
98-
97+
public OpenApiDocument()
98+
{
99+
Workspace = new OpenApiWorkspace();
100+
BaseUri = new(OpenApiConstants.BaseRegistryUri + Guid.NewGuid().ToString());
101+
}
102+
99103
/// <summary>
100104
/// Initializes a copy of an an <see cref="OpenApiDocument"/> object
101105
/// </summary>
@@ -441,30 +445,17 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList<OpenApiServer>
441445
}
442446

443447
/// <summary>
444-
/// 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
445450
/// </summary>
446-
/// <remarks>
447-
/// This method will be replaced by a LoadExternalReferences in the next major update to this library.
448-
/// Resolving references at load time is going to go away.
449-
/// </remarks>
450451
public IEnumerable<OpenApiError> ResolveReferences()
451452
{
452-
var resolver = new OpenApiReferenceResolver(this, false);
453+
var resolver = new ReferenceResolver(this);
453454
var walker = new OpenApiWalker(resolver);
454455
walker.Walk(this);
455456
return resolver.Errors;
456457
}
457458

458-
/// <summary>
459-
/// Walks the OpenApiDocument and sets the host document for all referenceable objects
460-
/// </summary>
461-
public void SetHostDocument()
462-
{
463-
var resolver = new HostDocumentResolver(this);
464-
var walker = new OpenApiWalker(resolver);
465-
walker.Walk(this);
466-
}
467-
468459
/// <summary>
469460
/// Load the referenced <see cref="IOpenApiReferenceable"/> object from a <see cref="OpenApiReference"/> object
470461
/// </summary>
@@ -488,6 +479,33 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference)
488479
return ResolveReference(reference, false);
489480
}
490481

482+
/// <summary>
483+
/// Resolves JsonSchema refs
484+
/// </summary>
485+
/// <param name="referenceUri"></param>
486+
/// <returns>A JsonSchema ref.</returns>
487+
public JsonSchema ResolveJsonSchemaReference(Uri referenceUri)
488+
{
489+
string uriLocation;
490+
string id = referenceUri.OriginalString.Split('/')?.Last();
491+
string relativePath = "/components/" + ReferenceType.Schema.GetDisplayName() + "/" + id;
492+
493+
if (referenceUri.OriginalString.StartsWith("#"))
494+
{
495+
// Local reference
496+
uriLocation = BaseUri + relativePath;
497+
}
498+
else
499+
{
500+
// External reference
501+
var externalUri = referenceUri.OriginalString.Split('#').First();
502+
var externalDocId = Workspace.GetDocumentId(externalUri);
503+
uriLocation = externalDocId + relativePath;
504+
}
505+
506+
return (JsonSchema)Workspace.ResolveReference<IBaseDocument>(uriLocation);
507+
}
508+
491509
/// <summary>
492510
/// Takes in an OpenApi document instance and generates its hash value
493511
/// </summary>
@@ -532,16 +550,6 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool
532550
return null;
533551
}
534552

535-
// Todo: Verify if we need to check to see if this external reference is actually targeted at this document.
536-
if (useExternal)
537-
{
538-
if (this.Workspace == null)
539-
{
540-
throw new ArgumentException(Properties.SRResource.WorkspaceRequredForExternalReferenceResolution);
541-
}
542-
return this.Workspace.ResolveReference(reference);
543-
}
544-
545553
if (!reference.Type.HasValue)
546554
{
547555
throw new ArgumentException(Properties.SRResource.LocalReferenceRequiresType);
@@ -562,51 +570,16 @@ internal IOpenApiReferenceable ResolveReference(OpenApiReference reference, bool
562570
return null;
563571
}
564572

565-
if (this.Components == null)
566-
{
567-
throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id));
568-
}
569-
570-
try
571-
{
572-
switch (reference.Type)
573-
{
574-
case ReferenceType.PathItem:
575-
return Components.PathItems[reference.Id];
576-
case ReferenceType.Response:
577-
return Components.Responses[reference.Id];
578-
579-
case ReferenceType.Parameter:
580-
return Components.Parameters[reference.Id];
581-
582-
case ReferenceType.Example:
583-
return Components.Examples[reference.Id];
584-
585-
case ReferenceType.RequestBody:
586-
return Components.RequestBodies[reference.Id];
573+
string uriLocation;
574+
string relativePath = "/components/" + reference.Type.GetDisplayName() + "/" + reference.Id;
587575

588-
case ReferenceType.Header:
589-
return Components.Headers[reference.Id];
576+
uriLocation = useExternal
577+
? Workspace.GetDocumentId(reference.ExternalResource)?.OriginalString + relativePath
578+
: BaseUri + relativePath;
590579

591-
case ReferenceType.SecurityScheme:
592-
return Components.SecuritySchemes[reference.Id];
593-
594-
case ReferenceType.Link:
595-
return Components.Links[reference.Id];
596-
597-
case ReferenceType.Callback:
598-
return Components.Callbacks[reference.Id];
599-
600-
default:
601-
throw new OpenApiException(Properties.SRResource.InvalidReferenceType);
602-
}
603-
}
604-
catch (KeyNotFoundException)
605-
{
606-
throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id));
607-
}
580+
return Workspace.ResolveReference<IOpenApiReferenceable>(uriLocation);
608581
}
609-
582+
610583
/// <summary>
611584
/// Parses a local file path or Url into an Open API document.
612585
/// </summary>
@@ -707,12 +680,6 @@ public JsonSchema FindSubschema(Json.Pointer.JsonPointer pointer, EvaluationOpti
707680
{
708681
throw new NotImplementedException();
709682
}
710-
711-
internal JsonSchema ResolveJsonSchemaReference(Uri reference)
712-
{
713-
var referencePath = string.Concat("https://registry", reference.OriginalString.Split('#').Last());
714-
return (JsonSchema)SchemaRegistry.Global.Get(new Uri(referencePath));
715-
}
716683
}
717684

718685
internal class FindSchemaReferences : OpenApiVisitorBase

src/Microsoft.OpenApi/Models/OpenApiExample.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public OpenApiExample(OpenApiExample example)
6868
{
6969
Summary = example?.Summary ?? Summary;
7070
Description = example?.Description ?? Description;
71-
Value = JsonNodeCloneHelper.Clone(example?.Value);
71+
Value = example?.Value ?? JsonNodeCloneHelper.Clone(example?.Value);
7272
ExternalValue = example?.ExternalValue ?? ExternalValue;
7373
Extensions = example?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(example.Extensions) : null;
7474
Reference = example?.Reference != null ? new(example?.Reference) : null;

src/Microsoft.OpenApi/Models/OpenApiHeader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ public OpenApiHeader(OpenApiHeader header)
114114
Style = header?.Style ?? Style;
115115
Explode = header?.Explode ?? Explode;
116116
AllowReserved = header?.AllowReserved ?? AllowReserved;
117-
_schema = JsonNodeCloneHelper.CloneJsonSchema(header?.Schema);
118-
Example = JsonNodeCloneHelper.Clone(header?.Example);
117+
Schema = header?.Schema != null ? JsonNodeCloneHelper.CloneJsonSchema(header?.Schema) : null;
118+
Example = header?.Example != null ? JsonNodeCloneHelper.Clone(header?.Example) : null;
119119
Examples = header?.Examples != null ? new Dictionary<string, OpenApiExample>(header.Examples) : null;
120120
Content = header?.Content != null ? new Dictionary<string, OpenApiMediaType>(header.Content) : null;
121121
Extensions = header?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(header.Extensions) : null;

src/Microsoft.OpenApi/Models/OpenApiParameter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,9 @@ public OpenApiParameter(OpenApiParameter parameter)
168168
Style = parameter?.Style ?? Style;
169169
Explode = parameter?.Explode ?? Explode;
170170
AllowReserved = parameter?.AllowReserved ?? AllowReserved;
171-
_schema = JsonNodeCloneHelper.CloneJsonSchema(parameter?.Schema);
171+
Schema = parameter?.Schema != null ? JsonNodeCloneHelper.CloneJsonSchema(parameter?.Schema) : null;
172172
Examples = parameter?.Examples != null ? new Dictionary<string, OpenApiExample>(parameter.Examples) : null;
173-
Example = JsonNodeCloneHelper.Clone(parameter?.Example);
173+
Example = parameter?.Example != null ? JsonNodeCloneHelper.Clone(parameter?.Example) : null;
174174
Content = parameter?.Content != null ? new Dictionary<string, OpenApiMediaType>(parameter.Content) : null;
175175
Extensions = parameter?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(parameter.Extensions) : null;
176176
AllowEmptyValue = parameter?.AllowEmptyValue ?? AllowEmptyValue;

src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ private OpenApiCallback Target
3333
/// <param name="hostDocument">The host OpenAPI document.</param>
3434
/// <param name="externalResource">Optional: External resource in the reference.
3535
/// It may be:
36-
/// 1. a absolute/relative file path, for example: ../commons/pet.json
36+
/// 1. an absolute/relative file path, for example: ../commons/pet.json
3737
/// 2. a Url, for example: http://localhost/pet.json
3838
/// </param>
3939
public OpenApiCallbackReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null)

src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ private OpenApiExample Target
2424
get
2525
{
2626
_target ??= Reference.HostDocument.ResolveReferenceTo<OpenApiExample>(_reference);
27-
return _target;
27+
OpenApiExample resolved = new OpenApiExample(_target);
28+
if (!string.IsNullOrEmpty(_description)) resolved.Description = _description;
29+
if (!string.IsNullOrEmpty(_summary)) resolved.Summary = _summary;
30+
return resolved;
2831
}
2932
}
3033

@@ -71,12 +74,12 @@ internal OpenApiExampleReference(OpenApiExample target, string referenceId)
7174
public override string Description
7275
{
7376
get => string.IsNullOrEmpty(_description) ? Target.Description : _description;
74-
set => _description = value;
77+
set => _description = value;
7578
}
7679

7780
/// <inheritdoc/>
7881
public override string Summary
79-
{
82+
{
8083
get => string.IsNullOrEmpty(_summary) ? Target.Summary : _summary;
8184
set => _summary = value;
8285
}

src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ private OpenApiHeader Target
2424
get
2525
{
2626
_target ??= Reference.HostDocument.ResolveReferenceTo<OpenApiHeader>(_reference);
27-
return _target;
27+
OpenApiHeader resolved = new OpenApiHeader(_target);
28+
if (!string.IsNullOrEmpty(_description)) resolved.Description = _description;
29+
return resolved;
2830
}
2931
}
3032

@@ -153,7 +155,7 @@ public override void SerializeAsV2(IOpenApiWriter writer)
153155
private void SerializeInternal(IOpenApiWriter writer,
154156
Action<IOpenApiWriter, IOpenApiReferenceable> action)
155157
{
156-
Utils.CheckArgumentNull(writer);;
158+
Utils.CheckArgumentNull(writer);
157159
action(writer, Target);
158160
}
159161
}

src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ private OpenApiLink Target
2222
get
2323
{
2424
_target ??= Reference.HostDocument.ResolveReferenceTo<OpenApiLink>(_reference);
25-
return _target;
25+
OpenApiLink resolved = new OpenApiLink(_target);
26+
if (!string.IsNullOrEmpty(_description)) resolved.Description = _description;
27+
return resolved;
2628
}
2729
}
2830

src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ private OpenApiParameter Target
2626
get
2727
{
2828
_target ??= Reference.HostDocument.ResolveReferenceTo<OpenApiParameter>(_reference);
29-
return _target;
29+
OpenApiParameter resolved = new OpenApiParameter(_target);
30+
if (!string.IsNullOrEmpty(_description)) resolved.Description = _description;
31+
return resolved;
3032
}
3133
}
3234

0 commit comments

Comments
 (0)