Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ public class BaseOpenApiReference : IOpenApiSerializable
/// </summary>
public bool IsFragment { get; init; }

private bool? _isStandalone;
/// <summary>
/// Gets or sets a flag indicating whether this reference points directly to a standalone external resource rather than a component within it.
/// </summary>
internal bool IsStandalone
{
get => _isStandalone ?? (Type == ReferenceType.Schema && IsExternal && Id is not null && string.Equals(ExternalResource, Id, StringComparison.Ordinal) && !Id.Contains("#"));
set => _isStandalone = value;
}

private OpenApiDocument? hostDocument;
/// <summary>
/// The OpenApiDocument that is hosting the OpenApiReference instance. This is used to enable dereferencing the reference.
Expand Down Expand Up @@ -148,6 +158,9 @@ public BaseOpenApiReference(BaseOpenApiReference reference)
Type = reference.Type;
Id = reference.Id;
HostDocument = reference.HostDocument;
IsFragment = reference.IsFragment;
IsStandalone = reference.IsStandalone;
_referenceV3 = reference._referenceV3;
}

/// <inheritdoc/>
Expand Down Expand Up @@ -241,6 +254,11 @@ public virtual void SerializeAsV2(IOpenApiWriter writer)

private string? GetExternalReferenceV3()
{
if (IsStandalone)
{
return ExternalResource;
}

if (Id != null)
{
if (IsFragment)
Expand Down Expand Up @@ -321,20 +339,24 @@ internal void SetJsonPointerPath(string pointer, ParsingContext context)
{
// Relative reference to internal JSON schema node/resource (e.g. "#/properties/b")
#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER || NET5_0_OR_GREATER
if (pointer.StartsWith("#/", StringComparison.OrdinalIgnoreCase) && !pointer.Contains("/components/schemas", StringComparison.OrdinalIgnoreCase))
if (pointer.StartsWith("#/", StringComparison.OrdinalIgnoreCase) && !pointer.Contains("/components/", StringComparison.OrdinalIgnoreCase))
#else
if (pointer.StartsWith("#/", StringComparison.OrdinalIgnoreCase) && !pointer.ToLowerInvariant().Contains("/components/schemas"))
if (pointer.StartsWith("#/", StringComparison.OrdinalIgnoreCase) && !pointer.ToLowerInvariant().Contains("/components/"))
#endif
{
ReferenceV3 = ResolveRelativePointer(context.GetLocation(), pointer);
}

// Absolute reference or anchor (e.g. "#/components/schemas/..." or full URL)
else if ((pointer.Contains('#') || pointer.StartsWith("http", StringComparison.OrdinalIgnoreCase))
&& !string.Equals(ReferenceV3, pointer, StringComparison.OrdinalIgnoreCase))
// Absolute reference, anchor, or relative pointer (e.g. "#/components/schemas/...", full URL, or "schemas/uuid.schema.json")
else if (!string.Equals(ReferenceV3, pointer, StringComparison.OrdinalIgnoreCase))
{
ReferenceV3 = pointer;
}

if (IsExternal)
{
IsStandalone = !pointer.Contains('#');
}
}

private static string ResolveRelativePointer(string nodeLocation, string relativeRef)
Expand Down
44 changes: 30 additions & 14 deletions src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
Expand Down Expand Up @@ -631,14 +631,21 @@ private static string ConvertByteArrayToString(byte[] hash)

string uriLocation;
var id = reference.Id;
if (!string.IsNullOrEmpty(id) && id!.Contains('/')) // this means its a URL reference
var referenceV3 = !string.IsNullOrEmpty(reference.ReferenceV3) ? reference.ReferenceV3! : string.Empty;
var isStandaloneExternal = reference.IsExternal &&
(reference.IsStandalone || (!string.IsNullOrEmpty(referenceV3) ? !referenceV3.Contains("#") : (reference.Type == ReferenceType.Schema && id is not null && !id.Contains("#") && id.Contains("/"))));

if (isStandaloneExternal)
{
uriLocation = id ?? reference.ExternalResource ?? string.Empty;
}
else if (!string.IsNullOrEmpty(id) && id!.Contains("/"))
{
uriLocation = id!;
}
else
{
string relativePath;
var referenceV3 = !string.IsNullOrEmpty(reference.ReferenceV3) ? reference.ReferenceV3! : string.Empty;

if (reference.Type is ReferenceType.Schema && useExternal && TryGetPlainNameFragment(referenceV3, out var plainNameFragment))
{
Expand Down Expand Up @@ -669,19 +676,28 @@ private static string ConvertByteArrayToString(byte[] hash)
: BaseUri + relativePath;
}

var absoluteUri =
#if NETSTANDARD2_1 || NETCOREAPP || NET5_0_OR_GREATER
uriLocation.StartsWith('#')
#else
uriLocation.StartsWith("#", StringComparison.OrdinalIgnoreCase)
#endif
switch
Uri? resolvedUri = null;
if (Uri.TryCreate(uriLocation, UriKind.Absolute, out var absUri))
{
true => new Uri(BaseUri, uriLocation).AbsoluteUri,
false => new Uri(uriLocation).AbsoluteUri
};
resolvedUri = absUri;
}
else if (useExternal && reference.ExternalResource is not null && Workspace?.GetDocumentId(reference.ExternalResource) is Uri extDocUri)
{
resolvedUri = extDocUri;
}
else if (BaseUri is not null && Uri.TryCreate(BaseUri, uriLocation, out var relCombinedUri))
{
resolvedUri = relCombinedUri;
}

if (resolvedUri is null)
{
return null;
}

var absoluteUri = resolvedUri.AbsoluteUri;

if (reference.Type is ReferenceType.Schema && absoluteUri.Contains('#') && parentSchema is not null)
if (reference.Type is ReferenceType.Schema && absoluteUri.Contains("#") && parentSchema is not null)
{
return Workspace?.ResolveJsonSchemaReference(absoluteUri, parentSchema);
}
Expand Down
27 changes: 17 additions & 10 deletions src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -45,18 +45,25 @@ internal async Task<OpenApiDiagnostic> LoadAsync(BaseOpenApiReference reference,
// If not already in workspace, load it and process references
if (item.ExternalResource is not null && !_workspace.Contains(item.ExternalResource))
{
var uri = new Uri(item.ExternalResource, UriKind.RelativeOrAbsolute);
var input = await _loader.LoadAsync(item.HostDocument!.BaseUri, uri, cancellationToken).ConfigureAwait(false);
var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken).ConfigureAwait(false);
// Merge diagnostics
if (result.Diagnostic != null)
try
{
diagnostic.AppendDiagnostic(result.Diagnostic, item.ExternalResource);
var uri = new Uri(item.ExternalResource, UriKind.RelativeOrAbsolute);
var input = await _loader.LoadAsync(item.HostDocument!.BaseUri, uri, cancellationToken).ConfigureAwait(false);
var result = await OpenApiDocument.LoadAsync(input, format, _readerSettings, cancellationToken).ConfigureAwait(false);
// Merge diagnostics
if (result.Diagnostic != null)
{
diagnostic.AppendDiagnostic(result.Diagnostic, item.ExternalResource);
}
if (result.Document != null)
{
var loadDiagnostic = await LoadAsync(item, result.Document, format, diagnostic, cancellationToken).ConfigureAwait(false);
diagnostic = loadDiagnostic;
}
}
if (result.Document != null)
catch (Exception ex) when (ex is not OperationCanceledException)
{
var loadDiagnostic = await LoadAsync(item, result.Document, format, diagnostic, cancellationToken).ConfigureAwait(false);
diagnostic = loadDiagnostic;
diagnostic.Errors.Add(new OpenApiError(string.Empty, ex.Message));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public static IOpenApiCallback LoadCallback(JsonNode node, OpenApiDocument hostD
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiCallbackReference(reference.Item1, hostDocument, reference.Item2);
var callbackReference = new OpenApiCallbackReference(reference.Item1, hostDocument, reference.Item2);
callbackReference.Reference.SetJsonPointerPath(pointer, context);
return callbackReference;
}

var domainObject = new OpenApiCallback();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ public static IOpenApiExample LoadExample(JsonNode node, OpenApiDocument hostDoc
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiExampleReference(reference.Item1, hostDocument, reference.Item2);
var exampleReference = new OpenApiExampleReference(reference.Item1, hostDocument, reference.Item2);
exampleReference.Reference.SetJsonPointerPath(pointer, context);
return exampleReference;
}

var example = new OpenApiExample();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ public static IOpenApiHeader LoadHeader(JsonNode node, OpenApiDocument hostDocum
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiHeaderReference(reference.Item1, hostDocument, reference.Item2);
var headerReference = new OpenApiHeaderReference(reference.Item1, hostDocument, reference.Item2);
headerReference.Reference.SetJsonPointerPath(pointer, context);
return headerReference;
}

var header = new OpenApiHeader();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ public static IOpenApiLink LoadLink(JsonNode node, OpenApiDocument hostDocument,
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiLinkReference(reference.Item1, hostDocument, reference.Item2);
var linkReference = new OpenApiLinkReference(reference.Item1, hostDocument, reference.Item2);
linkReference.Reference.SetJsonPointerPath(pointer, context);
return linkReference;
}

ParseMap(jsonObject, link, _linkFixedFields, _linkPatternFields, hostDocument, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,9 @@ public static IOpenApiParameter LoadParameter(JsonNode node, OpenApiDocument hos
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiParameterReference(reference.Item1, hostDocument, reference.Item2);
var parameterReference = new OpenApiParameterReference(reference.Item1, hostDocument, reference.Item2);
parameterReference.Reference.SetJsonPointerPath(pointer, context);
return parameterReference;
}

var parameter = new OpenApiParameter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public static IOpenApiPathItem LoadPathItem(JsonNode node, OpenApiDocument hostD
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiPathItemReference(reference.Item1, hostDocument, reference.Item2);
var pathItemReference = new OpenApiPathItemReference(reference.Item1, hostDocument, reference.Item2);
pathItemReference.Reference.SetJsonPointerPath(pointer, context);
return pathItemReference;
}

var pathItem = new OpenApiPathItem();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ public static IOpenApiRequestBody LoadRequestBody(JsonNode node, OpenApiDocument
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiRequestBodyReference(reference.Item1, hostDocument, reference.Item2);
var requestBodyReference = new OpenApiRequestBodyReference(reference.Item1, hostDocument, reference.Item2);
requestBodyReference.Reference.SetJsonPointerPath(pointer, context);
return requestBodyReference;
}

var requestBody = new OpenApiRequestBody();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ public static IOpenApiResponse LoadResponse(JsonNode node, OpenApiDocument hostD
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiResponseReference(reference.Item1, hostDocument, reference.Item2);
var responseReference = new OpenApiResponseReference(reference.Item1, hostDocument, reference.Item2);
responseReference.Reference.SetJsonPointerPath(pointer, context);
return responseReference;
}

var response = new OpenApiResponse();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ public static IOpenApiSecurityScheme LoadSecurityScheme(JsonNode node, OpenApiDo
if (pointer != null)
{
var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiSecuritySchemeReference(reference.Item1, hostDocument, reference.Item2);
var securitySchemeReference = new OpenApiSecuritySchemeReference(reference.Item1, hostDocument, reference.Item2);
securitySchemeReference.Reference.SetJsonPointerPath(pointer, context);
return securitySchemeReference;
}

var securityScheme = new OpenApiSecurityScheme();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static IOpenApiCallback LoadCallback(JsonNode node, OpenApiDocument hostD
var reference = GetReferenceIdAndExternalResource(pointer);
var callbackReference = new OpenApiCallbackReference(reference.Item1, hostDocument, reference.Item2);
callbackReference.Reference.SetMetadataFromJsonObject(jsonObject);
callbackReference.Reference.SetJsonPointerPath(pointer, context);
return callbackReference;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,25 @@ public static OpenApiDiscriminator LoadDiscriminator(JsonNode node, OpenApiDocum
public static OpenApiSchemaReference LoadMapping(JsonNode node, OpenApiDocument hostDocument, ParsingContext context)
{
var pointer = node.GetScalarValue() ?? throw new InvalidOperationException("Could not get a pointer reference");
if (IsBareSchemaName(pointer))
{
var schemaRef = new OpenApiSchemaReference(pointer, hostDocument);
schemaRef.Reference.SetJsonPointerPath(pointer, context);
return schemaRef;
}

var reference = GetReferenceIdAndExternalResource(pointer);
return new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2);
var result = new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2);
result.Reference.SetJsonPointerPath(pointer, context);
return result;
}

private static bool IsBareSchemaName(string pointer)
{
return !pointer.StartsWith("#", StringComparison.OrdinalIgnoreCase)
&& !pointer.Contains("/")
&& !pointer.Contains("#")
&& !pointer.Contains(":");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public static IOpenApiExample LoadExample(JsonNode node, OpenApiDocument hostDoc
var reference = GetReferenceIdAndExternalResource(pointer);
var exampleReference = new OpenApiExampleReference(reference.Item1, hostDocument, reference.Item2);
exampleReference.Reference.SetMetadataFromJsonObject(jsonObject);
exampleReference.Reference.SetJsonPointerPath(pointer, context);
return exampleReference;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public static IOpenApiHeader LoadHeader(JsonNode node, OpenApiDocument hostDocum
var reference = GetReferenceIdAndExternalResource(pointer);
var headerReference = new OpenApiHeaderReference(reference.Item1, hostDocument, reference.Item2);
headerReference.Reference.SetMetadataFromJsonObject(jsonObject);
headerReference.Reference.SetJsonPointerPath(pointer, context);
return headerReference;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static IOpenApiLink LoadLink(JsonNode node, OpenApiDocument hostDocument,
var reference = GetReferenceIdAndExternalResource(pointer);
var linkReference = new OpenApiLinkReference(reference.Item1, hostDocument, reference.Item2);
linkReference.Reference.SetMetadataFromJsonObject(jsonObject);
linkReference.Reference.SetJsonPointerPath(pointer, context);
return linkReference;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ public static IOpenApiParameter LoadParameter(JsonNode node, OpenApiDocument hos
var reference = GetReferenceIdAndExternalResource(pointer);
var parameterReference = new OpenApiParameterReference(reference.Item1, hostDocument, reference.Item2);
parameterReference.Reference.SetMetadataFromJsonObject(jsonObject);
parameterReference.Reference.SetJsonPointerPath(pointer, context);
return parameterReference;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static IOpenApiPathItem LoadPathItem(JsonNode node, OpenApiDocument hostD
var reference = GetReferenceIdAndExternalResource(pointer);
var pathItemReference = new OpenApiPathItemReference(reference.Item1, hostDocument, reference.Item2);
pathItemReference.Reference.SetMetadataFromJsonObject(jsonObject);
pathItemReference.Reference.SetJsonPointerPath(pointer, context);
return pathItemReference;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static IOpenApiRequestBody LoadRequestBody(JsonNode node, OpenApiDocument
var reference = GetReferenceIdAndExternalResource(pointer);
var requestBodyReference = new OpenApiRequestBodyReference(reference.Item1, hostDocument, reference.Item2);
requestBodyReference.Reference.SetMetadataFromJsonObject(jsonObject);
requestBodyReference.Reference.SetJsonPointerPath(pointer, context);
return requestBodyReference;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public static IOpenApiResponse LoadResponse(JsonNode node, OpenApiDocument hostD
var reference = GetReferenceIdAndExternalResource(pointer);
var responseReference = new OpenApiResponseReference(reference.Item1, hostDocument, reference.Item2);
responseReference.Reference.SetMetadataFromJsonObject(jsonObject);
responseReference.Reference.SetJsonPointerPath(pointer, context);
return responseReference;
}

Expand Down
Loading