diff --git a/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs b/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs
index 506161298..78295fc88 100644
--- a/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs
+++ b/src/Microsoft.OpenApi/Models/BaseOpenApiReference.cs
@@ -51,6 +51,16 @@ public class BaseOpenApiReference : IOpenApiSerializable
///
public bool IsFragment { get; init; }
+ private bool? _isStandalone;
+ ///
+ /// Gets or sets a flag indicating whether this reference points directly to a standalone external resource rather than a component within it.
+ ///
+ 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;
///
/// The OpenApiDocument that is hosting the OpenApiReference instance. This is used to enable dereferencing the reference.
@@ -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;
}
///
@@ -241,6 +254,11 @@ public virtual void SerializeAsV2(IOpenApiWriter writer)
private string? GetExternalReferenceV3()
{
+ if (IsStandalone)
+ {
+ return ExternalResource;
+ }
+
if (Id != null)
{
if (IsFragment)
@@ -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)
diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
index de2bb9d16..4e6bcbff6 100644
--- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
+++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs
@@ -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;
@@ -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))
{
@@ -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);
}
diff --git a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs
index e793aa985..a4bfcb9c8 100644
--- a/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs
+++ b/src/Microsoft.OpenApi/Reader/Services/OpenApiWorkspaceLoader.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Threading;
using System.Threading.Tasks;
@@ -45,18 +45,25 @@ internal async Task 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));
}
}
}
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiCallbackDeserializer.cs
index 686968e4c..95b55ceae 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiCallbackDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiCallbackDeserializer.cs
@@ -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();
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiExampleDeserializer.cs
index 03f587f58..ade0e7e06 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiExampleDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiExampleDeserializer.cs
@@ -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();
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs
index c82b47bd8..6a70bda45 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiHeaderDeserializer.cs
@@ -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();
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiLinkDeserializer.cs
index 37eb476b4..e47aa94d6 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiLinkDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiLinkDeserializer.cs
@@ -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);
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs
index 435515658..d51befb53 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiParameterDeserializer.cs
@@ -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();
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiPathItemDeserializer.cs
index b24619835..7b3a1fce3 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiPathItemDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiPathItemDeserializer.cs
@@ -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();
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiRequestBodyDeserializer.cs
index 8aee2e9a8..bd54de6df 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiRequestBodyDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiRequestBodyDeserializer.cs
@@ -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();
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs
index dba913db4..a5aacc271 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiResponseDeserializer.cs
@@ -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();
diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs
index 3f9d8be83..322273be6 100644
--- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSecuritySchemeDeserializer.cs
@@ -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();
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiCallbackDeserializer.cs
index f37f59d2b..5bbd656d1 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiCallbackDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiCallbackDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiDiscriminatorDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiDiscriminatorDeserializer.cs
index 38452fda2..9ecd0897b 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiDiscriminatorDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiDiscriminatorDeserializer.cs
@@ -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(":");
}
}
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiExampleDeserializer.cs
index 8c6fef31e..bddf504ba 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiExampleDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiExampleDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs
index 10eac3dea..ab56d7402 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiHeaderDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiLinkDeserializer.cs
index e9f07205d..3ac927a81 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiLinkDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiLinkDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs
index 177342fb4..deec75e4d 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiParameterDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiPathItemDeserializer.cs
index 101572f5b..dccb5adee 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiPathItemDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiPathItemDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs
index 1efb61a82..62675bdbd 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiRequestBodyDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs
index b8d64733d..a1992ca97 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiResponseDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs
index fac4b87c1..b7095da47 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSecuritySchemeDeserializer.cs
@@ -116,6 +116,7 @@ public static IOpenApiSecurityScheme LoadSecurityScheme(JsonNode node, OpenApiDo
var reference = GetReferenceIdAndExternalResource(pointer);
var securitySchemeReference = new OpenApiSecuritySchemeReference(reference.Item1, hostDocument, reference.Item2);
securitySchemeReference.Reference.SetMetadataFromJsonObject(jsonObject);
+ securitySchemeReference.Reference.SetJsonPointerPath(pointer, context);
return securitySchemeReference;
}
diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
index 24c49b8ff..4aa9c0f52 100644
--- a/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs
@@ -177,7 +177,7 @@ private static (string, string?) GetReferenceIdAndExternalResource(string pointe
var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase);
string? externalResource = null;
- if (isExternalResource && pointer.Contains('#'))
+ if (isExternalResource)
{
externalResource = pointer.Split('#')[0].TrimEnd('#');
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiCallbackDeserializer.cs
index 1c3b9dcab..83fa274bb 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiCallbackDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiCallbackDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiDiscriminatorDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiDiscriminatorDeserializer.cs
index a3aec0c53..49abc3ad8 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiDiscriminatorDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiDiscriminatorDeserializer.cs
@@ -51,8 +51,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(":");
}
}
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiExampleDeserializer.cs
index b83b25c65..8c038603d 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiExampleDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiExampleDeserializer.cs
@@ -66,6 +66,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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiHeaderDeserializer.cs
index f7cd702d3..639c97f23 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiHeaderDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiHeaderDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiLinkDeserializer.cs
index 5655d397d..912c8cbff 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiLinkDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiLinkDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs
index 7bff0f942..c09ec61b9 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiMediaTypeDeserializer.cs
@@ -97,6 +97,7 @@ public static IOpenApiMediaType LoadMediaType(JsonNode node, OpenApiDocument hos
var reference = GetReferenceIdAndExternalResource(pointer);
var mediaTypeReference = new OpenApiMediaTypeReference(reference.Item1, hostDocument, reference.Item2);
mediaTypeReference.Reference.SetMetadataFromJsonObject(jsonObject);
+ mediaTypeReference.Reference.SetJsonPointerPath(pointer, context);
return mediaTypeReference;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs
index 4a8300766..213376ae3 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiParameterDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiPathItemDeserializer.cs
index 3cc9bab03..b89dc80e0 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiPathItemDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiPathItemDeserializer.cs
@@ -82,6 +82,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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiRequestBodyDeserializer.cs
index a3ccbfcda..63ce237ef 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiRequestBodyDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiRequestBodyDeserializer.cs
@@ -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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs
index a0e36d017..c2f9f890d 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiResponseDeserializer.cs
@@ -59,6 +59,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;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs
index ca5ad4e48..745614d92 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs
@@ -114,6 +114,7 @@ public static IOpenApiSecurityScheme LoadSecurityScheme(JsonNode node, OpenApiDo
var reference = GetReferenceIdAndExternalResource(pointer);
var securitySchemeReference = new OpenApiSecuritySchemeReference(reference.Item1, hostDocument, reference.Item2);
securitySchemeReference.Reference.SetMetadataFromJsonObject(jsonObject);
+ securitySchemeReference.Reference.SetJsonPointerPath(pointer, context);
return securitySchemeReference;
}
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs
index c989da889..eb211d5cf 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs
@@ -177,7 +177,7 @@ private static (string, string?) GetReferenceIdAndExternalResource(string pointe
var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase);
string? externalResource = null;
- if (isExternalResource && pointer.Contains('#'))
+ if (isExternalResource)
{
externalResource = pointer.Split('#')[0].TrimEnd('#');
}
diff --git a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs
index 8872a8983..7c58d476a 100644
--- a/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs
+++ b/src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs
@@ -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;
@@ -949,10 +949,16 @@ public bool Contains(string location)
private Uri? ToLocationUrl(string location)
{
- if (BaseUrl is not null)
+ if (BaseUrl is not null && Uri.TryCreate(BaseUrl, location, out var relativeUri))
{
- return new(BaseUrl, location);
+ return relativeUri;
}
+
+ if (Uri.TryCreate(location, UriKind.Absolute, out var absoluteUri))
+ {
+ return absoluteUri;
+ }
+
return null;
}
}
diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs
index 34f4f2d45..f7a9906b9 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs
@@ -124,6 +124,46 @@ public async Task LoadDocumentWithExternalReferencesInSubDirectories()
}
}, petSchemaTarget);
}
+
+ [Fact]
+ public async Task LoadDocumentAsync_WhenLoaderThrowsOperationCanceledException_PropagatesCancellation()
+ {
+ var settings = new OpenApiReaderSettings
+ {
+ LoadExternalRefs = true,
+ CustomExternalLoader = new CancellingStreamLoader(),
+ BaseUrl = new("file://c:\\"),
+ };
+ settings.AddYamlReader();
+
+ await Assert.ThrowsAnyAsync(async () =>
+ await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings, token: TestContext.Current.CancellationToken));
+ }
+
+ [Fact]
+ public async Task LoadDocumentAsync_WhenCancellationTokenCanceled_PropagatesCancellation()
+ {
+ using var cts = new CancellationTokenSource();
+ cts.Cancel();
+
+ var settings = new OpenApiReaderSettings
+ {
+ LoadExternalRefs = true,
+ CustomExternalLoader = new ResourceLoader(),
+ BaseUrl = new("file://c:\\"),
+ };
+ settings.AddYamlReader();
+
+ await Assert.ThrowsAnyAsync(async () =>
+ await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings, token: cts.Token));
+ }
+ }
+
+ public class CancellingStreamLoader : IStreamLoader
+ {
+ public Stream Load(Uri uri) => throw new OperationCanceledException();
+ public Task LoadAsync(Uri baseUrl, Uri uri, CancellationToken cancellationToken = default)
+ => Task.FromException(new OperationCanceledException(cancellationToken));
}
public class MockLoader : IStreamLoader
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiComponentsTests.cs
index 01f7d97a8..7c6c7ba6c 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiComponentsTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiComponentsTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.OpenApi.Reader;
@@ -14,7 +14,7 @@ public class OpenApiComponentsTests
[InlineData("A:\\Dir\\File.json#/components/schemas/ExternalAbsWindowsPathModel", "ExternalAbsWindowsPathModel", "A:\\Dir\\File.json")]
[InlineData("/Dir/File.json#/components/schemas/ExternalAbsUnixPathModel", "ExternalAbsUnixPathModel", "/Dir/File.json")]
[InlineData("https://host.lan:1234/path/to/file/resource.json#/components/schemas/ExternalHttpsModel", "ExternalHttpsModel", "https://host.lan:1234/path/to/file/resource.json")]
- [InlineData("File.json", "File.json", null)]
+ [InlineData("File.json", "File.json", "File.json")]
public void ParseExternalSchemaReferenceShouldSucceed(string reference, string referenceId, string externalResource)
{
var input = $@"{{
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDiscriminatorTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDiscriminatorTests.cs
index 854afad9e..42dbbd429 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDiscriminatorTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDiscriminatorTests.cs
@@ -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.Collections.Generic;
@@ -42,5 +42,170 @@ public async Task ParseBasicDiscriminatorShouldSucceed()
DefaultMapping = new OpenApiSchemaReference("Animal", openApiDocument)
}, discriminator);
}
+
+ [Fact]
+ public void ParseDiscriminatorWithBareSchemaNameMappingResolvesLocalTarget()
+ {
+ var openApi = """
+ {
+ "openapi": "3.1.1",
+ "info": { "title": "Repro", "version": "1" },
+ "paths": {},
+ "components": {
+ "schemas": {
+ "Dog": {
+ "type": "object",
+ "properties": { "bark": { "type": "boolean" } }
+ },
+ "Pet": {
+ "type": "object",
+ "required": [ "petType" ],
+ "properties": {
+ "petType": { "type": "string" }
+ },
+ "discriminator": {
+ "propertyName": "petType",
+ "mapping": {
+ "dog": "Dog"
+ },
+ "x-oas-default-mapping": "Dog"
+ }
+ }
+ }
+ }
+ }
+ """;
+
+ var readResult = OpenApiDocument.Parse(openApi, "json");
+ var doc = readResult.Document;
+ Assert.NotNull(doc);
+ Assert.Empty(readResult.Diagnostic.Errors);
+
+ var petSchema = Assert.IsType(doc.Components.Schemas["Pet"]);
+ Assert.NotNull(petSchema.Discriminator);
+
+ var dogMapping = petSchema.Discriminator.Mapping["dog"];
+ Assert.False(dogMapping.Reference.IsExternal);
+ Assert.Null(dogMapping.Reference.ExternalResource);
+ Assert.Equal("Dog", dogMapping.Reference.Id);
+
+ var dogSchema = Assert.IsType(doc.Components.Schemas["Dog"]);
+ Assert.Same(dogSchema, dogMapping.Target);
+
+ Assert.NotNull(petSchema.Discriminator.DefaultMapping);
+ Assert.False(petSchema.Discriminator.DefaultMapping.Reference.IsExternal);
+ Assert.Same(dogSchema, petSchema.Discriminator.DefaultMapping.Target);
+ }
+
+ [Theory]
+ [InlineData("Dog.json")]
+ [InlineData("Dog.yaml")]
+ [InlineData("Dog.yml")]
+ public void ParseDiscriminatorWithFileSuffixedBareSchemaNameResolvesLocalTarget(string schemaName)
+ {
+ var openApi = $$"""
+ {
+ "openapi": "3.1.1",
+ "info": { "title": "Repro", "version": "1" },
+ "paths": {},
+ "components": {
+ "schemas": {
+ "{{schemaName}}": {
+ "type": "object",
+ "properties": { "bark": { "type": "boolean" } }
+ },
+ "Pet": {
+ "type": "object",
+ "required": [ "petType" ],
+ "properties": {
+ "petType": { "type": "string" }
+ },
+ "discriminator": {
+ "propertyName": "petType",
+ "mapping": {
+ "dog": "{{schemaName}}"
+ },
+ "x-oas-default-mapping": "{{schemaName}}"
+ }
+ }
+ }
+ }
+ }
+ """;
+
+ var readResult = OpenApiDocument.Parse(openApi, "json");
+ var doc = readResult.Document;
+ Assert.NotNull(doc);
+ Assert.Empty(readResult.Diagnostic.Errors);
+
+ var petSchema = Assert.IsType(doc.Components.Schemas["Pet"]);
+ Assert.NotNull(petSchema.Discriminator);
+
+ var dogMapping = petSchema.Discriminator.Mapping["dog"];
+ Assert.False(dogMapping.Reference.IsExternal);
+ Assert.Null(dogMapping.Reference.ExternalResource);
+ Assert.Equal(schemaName, dogMapping.Reference.Id);
+
+ var dogSchema = Assert.IsType(doc.Components.Schemas[schemaName]);
+ Assert.Same(dogSchema, dogMapping.Target);
+
+ Assert.NotNull(petSchema.Discriminator.DefaultMapping);
+ Assert.False(petSchema.Discriminator.DefaultMapping.Reference.IsExternal);
+ Assert.Same(dogSchema, petSchema.Discriminator.DefaultMapping.Target);
+ }
+
+ [Fact]
+ public void ParseDiscriminatorWithForwardDeclaredSchemaMappingResolvesLocalTarget()
+ {
+ var openApi = """
+ {
+ "openapi": "3.1.1",
+ "info": { "title": "Repro", "version": "1" },
+ "paths": {},
+ "components": {
+ "schemas": {
+ "Pet": {
+ "type": "object",
+ "required": [ "petType" ],
+ "properties": {
+ "petType": { "type": "string" }
+ },
+ "discriminator": {
+ "propertyName": "petType",
+ "mapping": {
+ "dog": "Dog.json"
+ },
+ "x-oas-default-mapping": "Dog.json"
+ }
+ },
+ "Dog.json": {
+ "type": "object",
+ "properties": { "bark": { "type": "boolean" } }
+ }
+ }
+ }
+ }
+ """;
+
+ var readResult = OpenApiDocument.Parse(openApi, "json");
+ var doc = readResult.Document;
+ Assert.NotNull(doc);
+ Assert.Empty(readResult.Diagnostic.Errors);
+
+ var petSchema = Assert.IsType(doc.Components.Schemas["Pet"]);
+ Assert.NotNull(petSchema.Discriminator);
+
+ var dogMapping = petSchema.Discriminator.Mapping["dog"];
+ Assert.False(dogMapping.Reference.IsExternal);
+ Assert.Null(dogMapping.Reference.ExternalResource);
+ Assert.Equal("Dog.json", dogMapping.Reference.Id);
+
+ var dogSchema = Assert.IsType(doc.Components.Schemas["Dog.json"]);
+ Assert.Same(dogSchema, dogMapping.Target);
+
+ Assert.NotNull(petSchema.Discriminator.DefaultMapping);
+ Assert.False(petSchema.Discriminator.DefaultMapping.Reference.IsExternal);
+ Assert.Same(dogSchema, petSchema.Discriminator.DefaultMapping.Target);
+ }
}
}
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaReferenceDeserializerTests.cs
index 782ae6e45..8010092c4 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaReferenceDeserializerTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaReferenceDeserializerTests.cs
@@ -57,4 +57,31 @@ public void ShouldDeserializeSchemaReferenceAnnotations()
Assert.Equal("example reference value", resultReference.Examples[0]?.ToString());
Assert.NotNull(resultReference.Target);
}
+
+ [Theory]
+ [InlineData("schemas/uuid.schema.json", "schemas/uuid.schema.json", "schemas/uuid.schema.json")]
+ [InlineData("./schemas/uuid.schema.json", "./schemas/uuid.schema.json", "./schemas/uuid.schema.json")]
+ [InlineData("uuid.schema.json", "uuid.schema.json", "uuid.schema.json")]
+ [InlineData("https://example.com/schemas/resource.json", "https://example.com/schemas/resource.json", "https://example.com/schemas/resource.json")]
+ public void ShouldDeserializeExternalSchemaReferenceWithoutFragment(string refPointer, string expectedId, string expectedExternalResource)
+ {
+ var json = $$"""
+ {
+ "$ref": "{{refPointer}}"
+ }
+ """;
+
+ var hostDocument = new OpenApiDocument();
+ var jsonNode = JsonNode.Parse(json);
+
+ var result = OpenApiV31Deserializer.LoadSchema(jsonNode, hostDocument, new ParsingContext(new()));
+
+ Assert.NotNull(result);
+ var resultReference = Assert.IsType(result);
+
+ Assert.True(resultReference.Reference.IsExternal);
+ Assert.Equal(expectedId, resultReference.Reference.Id);
+ Assert.Equal(expectedExternalResource, resultReference.Reference.ExternalResource);
+ Assert.Equal(refPointer, resultReference.Reference.ReferenceV3);
+ }
}
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs
index d829c4d5a..10107301c 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
@@ -747,5 +747,98 @@ public void ExitsEarlyOnCyclicalReferences()
Assert.Equal("#/components/schemas/Category", parentSchemaRef.Reference.ReferenceV3);
Assert.NotNull(parentSchemaRef.Target);
}
+
+ [Fact]
+ public void ParseDocumentWithRelativeReferenceWithoutFragmentDoesNotThrowUriFormatException()
+ {
+ var openApi = """
+ {
+ "openapi": "3.1.1",
+ "info": { "title": "Repro", "version": "1" },
+ "paths": {},
+ "components": {
+ "schemas": {
+ "Request": {
+ "type": "object",
+ "properties": { "attemptId": { "$ref": "schemas/uuid.schema.json" } }
+ }
+ }
+ }
+ }
+ """;
+
+ var readResult = OpenApiDocument.Parse(openApi, "json");
+ var doc = readResult.Document;
+ var diagnostic = readResult.Diagnostic;
+
+ Assert.NotNull(doc);
+ Assert.Empty(diagnostic.Errors);
+
+ var requestSchema = Assert.IsType(doc.Components.Schemas["Request"]);
+ var attemptIdRef = Assert.IsType(requestSchema.Properties["attemptId"]);
+ Assert.True(attemptIdRef.Reference.IsExternal);
+ Assert.Equal("schemas/uuid.schema.json", attemptIdRef.Reference.ExternalResource);
+ Assert.Equal("schemas/uuid.schema.json", attemptIdRef.Reference.Id);
+
+ Assert.Null(attemptIdRef.Target);
+
+ using var writer = new StringWriter();
+ doc.SerializeAsV31(new OpenApiJsonWriter(writer));
+ var serialized = writer.ToString();
+ Assert.Contains("\"$ref\": \"schemas/uuid.schema.json\"", serialized);
+ Assert.DoesNotContain("#/components/schemas/schemas/uuid.schema.json", serialized);
+ }
+
+ [Fact]
+ public void ParseDocumentWithExternalNonSchemaReferenceCollidingWithDocumentNamePreservesComponentFragment()
+ {
+ var openApi = """
+ {
+ "openapi": "3.1.1",
+ "info": { "title": "Repro", "version": "1" },
+ "paths": {
+ "/items": {
+ "get": {
+ "parameters": [
+ { "$ref": "common.json#/components/parameters/common.json" }
+ ],
+ "responses": {
+ "200": { "$ref": "common.json#/components/responses/common.json" }
+ }
+ }
+ }
+ }
+ }
+ """;
+
+ var readResult = OpenApiDocument.Parse(openApi, "json");
+ var doc = readResult.Document;
+ var diagnostic = readResult.Diagnostic;
+
+ Assert.NotNull(doc);
+ Assert.Empty(diagnostic.Errors);
+
+ var pathItem = doc.Paths["/items"];
+ var operation = pathItem.Operations[HttpMethod.Get];
+
+ var paramRef = Assert.IsType(operation.Parameters[0]);
+ Assert.True(paramRef.Reference.IsExternal);
+ Assert.Equal("common.json", paramRef.Reference.ExternalResource);
+ Assert.Equal("common.json", paramRef.Reference.Id);
+ Assert.Equal("common.json#/components/parameters/common.json", paramRef.Reference.ReferenceV3);
+
+ var responseRef = Assert.IsType(operation.Responses["200"]);
+ Assert.True(responseRef.Reference.IsExternal);
+ Assert.Equal("common.json", responseRef.Reference.ExternalResource);
+ Assert.Equal("common.json", responseRef.Reference.Id);
+ Assert.Equal("common.json#/components/responses/common.json", responseRef.Reference.ReferenceV3);
+
+ using var writer = new StringWriter();
+ doc.SerializeAsV31(new OpenApiJsonWriter(writer));
+ var serialized = writer.ToString();
+ Assert.Contains("\"$ref\": \"common.json#/components/parameters/common.json\"", serialized);
+ Assert.Contains("\"$ref\": \"common.json#/components/responses/common.json\"", serialized);
+ Assert.DoesNotContain("\"$ref\": \"common.json\"", serialized);
+ }
}
}
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiComponentsTests.cs
index da82e35e7..2128454f9 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiComponentsTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiComponentsTests.cs
@@ -14,7 +14,7 @@ public class OpenApiComponentsTests
[InlineData("A:\\Dir\\File.json#/components/schemas/ExternalAbsWindowsPathModel", "ExternalAbsWindowsPathModel", "A:\\Dir\\File.json")]
[InlineData("/Dir/File.json#/components/schemas/ExternalAbsUnixPathModel", "ExternalAbsUnixPathModel", "/Dir/File.json")]
[InlineData("https://host.lan:1234/path/to/file/resource.json#/components/schemas/ExternalHttpsModel", "ExternalHttpsModel", "https://host.lan:1234/path/to/file/resource.json")]
- [InlineData("File.json", "File.json", null)]
+ [InlineData("File.json", "File.json", "File.json")]
public void ParseExternalSchemaReferenceShouldSucceed(string reference, string referenceId, string externalResource)
{
var input = $@"{{
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDiscriminatorTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDiscriminatorTests.cs
index c571b0ce6..fffdc7c52 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDiscriminatorTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDiscriminatorTests.cs
@@ -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.Collections.Generic;
@@ -42,5 +42,170 @@ public async Task ParseBasicDiscriminatorShouldSucceed()
DefaultMapping = new OpenApiSchemaReference("Animal", openApiDocument)
}, discriminator);
}
+
+ [Fact]
+ public void ParseDiscriminatorWithBareSchemaNameMappingResolvesLocalTarget()
+ {
+ var openApi = """
+ {
+ "openapi": "3.2.0",
+ "info": { "title": "Repro", "version": "1" },
+ "paths": {},
+ "components": {
+ "schemas": {
+ "Dog": {
+ "type": "object",
+ "properties": { "bark": { "type": "boolean" } }
+ },
+ "Pet": {
+ "type": "object",
+ "required": [ "petType" ],
+ "properties": {
+ "petType": { "type": "string" }
+ },
+ "discriminator": {
+ "propertyName": "petType",
+ "mapping": {
+ "dog": "Dog"
+ },
+ "defaultMapping": "Dog"
+ }
+ }
+ }
+ }
+ }
+ """;
+
+ var readResult = OpenApiDocument.Parse(openApi, "json");
+ var doc = readResult.Document;
+ Assert.NotNull(doc);
+ Assert.Empty(readResult.Diagnostic.Errors);
+
+ var petSchema = Assert.IsType(doc.Components.Schemas["Pet"]);
+ Assert.NotNull(petSchema.Discriminator);
+
+ var dogMapping = petSchema.Discriminator.Mapping["dog"];
+ Assert.False(dogMapping.Reference.IsExternal);
+ Assert.Null(dogMapping.Reference.ExternalResource);
+ Assert.Equal("Dog", dogMapping.Reference.Id);
+
+ var dogSchema = Assert.IsType(doc.Components.Schemas["Dog"]);
+ Assert.Same(dogSchema, dogMapping.Target);
+
+ Assert.NotNull(petSchema.Discriminator.DefaultMapping);
+ Assert.False(petSchema.Discriminator.DefaultMapping.Reference.IsExternal);
+ Assert.Same(dogSchema, petSchema.Discriminator.DefaultMapping.Target);
+ }
+
+ [Theory]
+ [InlineData("Dog.json")]
+ [InlineData("Dog.yaml")]
+ [InlineData("Dog.yml")]
+ public void ParseDiscriminatorWithFileSuffixedBareSchemaNameResolvesLocalTarget(string schemaName)
+ {
+ var openApi = $$"""
+ {
+ "openapi": "3.2.0",
+ "info": { "title": "Repro", "version": "1" },
+ "paths": {},
+ "components": {
+ "schemas": {
+ "{{schemaName}}": {
+ "type": "object",
+ "properties": { "bark": { "type": "boolean" } }
+ },
+ "Pet": {
+ "type": "object",
+ "required": [ "petType" ],
+ "properties": {
+ "petType": { "type": "string" }
+ },
+ "discriminator": {
+ "propertyName": "petType",
+ "mapping": {
+ "dog": "{{schemaName}}"
+ },
+ "defaultMapping": "{{schemaName}}"
+ }
+ }
+ }
+ }
+ }
+ """;
+
+ var readResult = OpenApiDocument.Parse(openApi, "json");
+ var doc = readResult.Document;
+ Assert.NotNull(doc);
+ Assert.Empty(readResult.Diagnostic.Errors);
+
+ var petSchema = Assert.IsType(doc.Components.Schemas["Pet"]);
+ Assert.NotNull(petSchema.Discriminator);
+
+ var dogMapping = petSchema.Discriminator.Mapping["dog"];
+ Assert.False(dogMapping.Reference.IsExternal);
+ Assert.Null(dogMapping.Reference.ExternalResource);
+ Assert.Equal(schemaName, dogMapping.Reference.Id);
+
+ var dogSchema = Assert.IsType(doc.Components.Schemas[schemaName]);
+ Assert.Same(dogSchema, dogMapping.Target);
+
+ Assert.NotNull(petSchema.Discriminator.DefaultMapping);
+ Assert.False(petSchema.Discriminator.DefaultMapping.Reference.IsExternal);
+ Assert.Same(dogSchema, petSchema.Discriminator.DefaultMapping.Target);
+ }
+
+ [Fact]
+ public void ParseDiscriminatorWithForwardDeclaredSchemaMappingResolvesLocalTarget()
+ {
+ var openApi = """
+ {
+ "openapi": "3.2.0",
+ "info": { "title": "Repro", "version": "1" },
+ "paths": {},
+ "components": {
+ "schemas": {
+ "Pet": {
+ "type": "object",
+ "required": [ "petType" ],
+ "properties": {
+ "petType": { "type": "string" }
+ },
+ "discriminator": {
+ "propertyName": "petType",
+ "mapping": {
+ "dog": "Dog.json"
+ },
+ "defaultMapping": "Dog.json"
+ }
+ },
+ "Dog.json": {
+ "type": "object",
+ "properties": { "bark": { "type": "boolean" } }
+ }
+ }
+ }
+ }
+ """;
+
+ var readResult = OpenApiDocument.Parse(openApi, "json");
+ var doc = readResult.Document;
+ Assert.NotNull(doc);
+ Assert.Empty(readResult.Diagnostic.Errors);
+
+ var petSchema = Assert.IsType(doc.Components.Schemas["Pet"]);
+ Assert.NotNull(petSchema.Discriminator);
+
+ var dogMapping = petSchema.Discriminator.Mapping["dog"];
+ Assert.False(dogMapping.Reference.IsExternal);
+ Assert.Null(dogMapping.Reference.ExternalResource);
+ Assert.Equal("Dog.json", dogMapping.Reference.Id);
+
+ var dogSchema = Assert.IsType(doc.Components.Schemas["Dog.json"]);
+ Assert.Same(dogSchema, dogMapping.Target);
+
+ Assert.NotNull(petSchema.Discriminator.DefaultMapping);
+ Assert.False(petSchema.Discriminator.DefaultMapping.Reference.IsExternal);
+ Assert.Same(dogSchema, petSchema.Discriminator.DefaultMapping.Target);
+ }
}
}
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs
index 100e72b4e..63777e626 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs
@@ -57,5 +57,32 @@ public void ShouldDeserializeSchemaReferenceAnnotations()
Assert.Equal("example reference value", resultReference.Examples[0]?.ToString());
Assert.NotNull(resultReference.Target);
}
+
+ [Theory]
+ [InlineData("schemas/uuid.schema.json", "schemas/uuid.schema.json", "schemas/uuid.schema.json")]
+ [InlineData("./schemas/uuid.schema.json", "./schemas/uuid.schema.json", "./schemas/uuid.schema.json")]
+ [InlineData("uuid.schema.json", "uuid.schema.json", "uuid.schema.json")]
+ [InlineData("https://example.com/schemas/resource.json", "https://example.com/schemas/resource.json", "https://example.com/schemas/resource.json")]
+ public void ShouldDeserializeExternalSchemaReferenceWithoutFragment(string refPointer, string expectedId, string expectedExternalResource)
+ {
+ var json = $$"""
+ {
+ "$ref": "{{refPointer}}"
+ }
+ """;
+
+ var hostDocument = new OpenApiDocument();
+ var jsonNode = JsonNode.Parse(json);
+
+ var result = OpenApiV32Deserializer.LoadSchema(jsonNode, hostDocument, new ParsingContext(new()));
+
+ Assert.NotNull(result);
+ var resultReference = Assert.IsType(result);
+
+ Assert.True(resultReference.Reference.IsExternal);
+ Assert.Equal(expectedId, resultReference.Reference.Id);
+ Assert.Equal(expectedExternalResource, resultReference.Reference.ExternalResource);
+ Assert.Equal(refPointer, resultReference.Reference.ReferenceV3);
+ }
}
diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
index 774d9ff2a..f3322a396 100644
--- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
@@ -2754,6 +2754,100 @@ public void GetOperationById_ThrowsOnNullOrEmptyId()
Assert.Throws(() => doc.GetOperationById(string.Empty));
}
+ [Theory]
+ [InlineData("schemas/uuid.schema.json")]
+ [InlineData("./schemas/uuid.schema.json")]
+ [InlineData("uuid.schema.json")]
+ public void ResolveReference_RelativeReferenceWithoutFragmentDoesNotThrowUriFormatException(string referenceId)
+ {
+ var doc = new OpenApiDocument
+ {
+ Info = new OpenApiInfo { Title = "Test", Version = "1.0" },
+ BaseUri = new Uri("https://example.com/api/openapi.json"),
+ };
+
+ var schemaRef = new OpenApiSchemaReference(referenceId, doc, referenceId);
+
+ var result = doc.ResolveReferenceTo(schemaRef.Reference, null);
+ Assert.Null(result);
+ Assert.Null(schemaRef.Target);
+ }
+
+ [Fact]
+ public void ResolveReference_RelativeReferenceWithoutFragmentResolvesAgainstWorkspace()
+ {
+ var workspace = new OpenApiWorkspace(new Uri("https://example.com/api/"));
+ var targetSchema = new OpenApiSchema { Type = JsonSchemaType.String, Description = "UUID schema" };
+ workspace.RegisterComponent("https://example.com/api/schemas/uuid.schema.json", targetSchema);
+
+ var doc = new OpenApiDocument
+ {
+ Info = new OpenApiInfo { Title = "Test", Version = "1.0" },
+ BaseUri = new Uri("https://example.com/api/openapi.json"),
+ Workspace = workspace,
+ };
+
+ var schemaRef = new OpenApiSchemaReference("schemas/uuid.schema.json", doc, "schemas/uuid.schema.json");
+
+ var result = doc.ResolveReferenceTo(schemaRef.Reference, null);
+ Assert.Same(targetSchema, result);
+ Assert.Same(targetSchema, schemaRef.Target);
+ }
+
+ [Fact]
+ public void ResolveReference_ExternalComponentCollidingWithDocumentNameResolvesComponent()
+ {
+ var workspace = new OpenApiWorkspace(new Uri("https://example.com/api/"));
+ var targetSchema = new OpenApiSchema { Type = JsonSchemaType.String, Description = "Component named common.json" };
+ workspace.AddDocumentId("common.json", new Uri("https://example.com/api/common.json"));
+ workspace.RegisterComponent("https://example.com/api/common.json#/components/schemas/common.json", targetSchema);
+
+ var doc = new OpenApiDocument
+ {
+ Info = new OpenApiInfo { Title = "Test", Version = "1.0" },
+ BaseUri = new Uri("https://example.com/api/openapi.json"),
+ Workspace = workspace,
+ };
+
+ var schemaRef = new OpenApiSchemaReference("common.json", doc, "common.json");
+ schemaRef.Reference.SetJsonPointerPath("common.json#/components/schemas/common.json", new Microsoft.OpenApi.Reader.ParsingContext(new()));
+
+ var result = doc.ResolveReferenceTo(schemaRef.Reference, null);
+ Assert.Same(targetSchema, result);
+ Assert.Same(targetSchema, schemaRef.Target);
+ }
+
+ [Fact]
+ public void ResolveReference_ExternalNonSchemaComponentCollidingWithDocumentNameResolvesComponent()
+ {
+ var workspace = new OpenApiWorkspace(new Uri("https://example.com/api/"));
+ var targetResponse = new OpenApiResponse { Description = "Response named common.json" };
+ var targetParam = new OpenApiParameter { Name = "common.json", In = ParameterLocation.Query };
+ workspace.AddDocumentId("common.json", new Uri("https://example.com/api/common.json"));
+ workspace.RegisterComponent("https://example.com/api/common.json#/components/responses/common.json", targetResponse);
+ workspace.RegisterComponent("https://example.com/api/common.json#/components/parameters/common.json", targetParam);
+
+ var doc = new OpenApiDocument
+ {
+ Info = new OpenApiInfo { Title = "Test", Version = "1.0" },
+ BaseUri = new Uri("https://example.com/api/openapi.json"),
+ Workspace = workspace,
+ };
+
+ // Test publicly constructed non-schema reference
+ var responseRef = new OpenApiResponseReference("common.json", doc, "common.json");
+ Assert.Equal("common.json#/components/responses/common.json", responseRef.Reference.ReferenceV3);
+ var resultResponse = doc.ResolveReferenceTo(responseRef.Reference, null);
+ Assert.Same(targetResponse, resultResponse);
+ Assert.Same(targetResponse, responseRef.Target);
+
+ var paramRef = new OpenApiParameterReference("common.json", doc, "common.json");
+ Assert.Equal("common.json#/components/parameters/common.json", paramRef.Reference.ReferenceV3);
+ var resultParam = doc.ResolveReferenceTo(paramRef.Reference, null);
+ Assert.Same(targetParam, resultParam);
+ Assert.Same(targetParam, paramRef.Target);
+ }
+
[Fact]
public void OpenApiDocumentCopyConstructorPreservesPathsExtensions()
{
diff --git a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs
index 8bb018e9f..4b67dc13b 100644
--- a/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs
@@ -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;
@@ -172,6 +172,36 @@ private static OpenApiDocument CreateCommonDocument()
return doc;
}
+
+ [Fact]
+ public void OpenApiWorkspacesCanResolveAbsoluteUriReferences()
+ {
+ var workspace = new OpenApiWorkspace();
+ var targetSchema = new OpenApiSchema
+ {
+ Type = JsonSchemaType.String,
+ Description = "Schema at absolute URI"
+ };
+ workspace.RegisterComponent("https://example.com/schemas/uuid.schema.json", targetSchema);
+
+ var resolved = workspace.ResolveReference("https://example.com/schemas/uuid.schema.json");
+ Assert.Same(targetSchema, resolved);
+ }
+
+ [Fact]
+ public void OpenApiWorkspacesResolvesRootRelativeUriAgainstBaseUrl()
+ {
+ var workspace = new OpenApiWorkspace(new Uri("https://example.com/base/"));
+ var targetSchema = new OpenApiSchema
+ {
+ Type = JsonSchemaType.String,
+ Description = "Schema at root-relative URI"
+ };
+ workspace.RegisterComponent("https://example.com/schemas/common.json", targetSchema);
+
+ var resolved = workspace.ResolveReference("/schemas/common.json");
+ Assert.Same(targetSchema, resolved);
+ }
}
public static class OpenApiFactoryExtensions