Skip to content

fix: handle relative schema references without fragment and prevent UriFormatException (#3148) - #3153

Open
Hardik K (hardikkaurani) wants to merge 3 commits into
microsoft:mainfrom
hardikkaurani:fix/relative-ref-without-fragment-uri-format
Open

Hardik K (hardikkaurani) wants to merge 3 commits into
microsoft:mainfrom
hardikkaurani:fix/relative-ref-without-fragment-uri-format

Conversation

@hardikkaurani

Copy link
Copy Markdown
Contributor

Resolves #3148

Summary of Changes

  1. External Resource Deserialization (OpenAPI 3.1 & 3.2):

    • In OpenApiV31Deserializer.GetReferenceIdAndExternalResource and OpenApiV32Deserializer.GetReferenceIdAndExternalResource, removed the && pointer.Contains('#') constraint when determining externalResource.
    • References to standalone external resources without a fragment (such as schemas/uuid.schema.json, ./schemas/uuid.schema.json, or absolute schema URLs) now properly set ExternalResource and evaluate IsExternal = true, aligning behavior with OpenApiV3Deserializer and the OpenAPI 3.1/3.2 specification.
  2. Safe Relative URI Resolution:

    • In OpenApiDocument.ResolveReference, standalone external references (reference.IsExternal && reference.ExternalResource == id) are routed directly to uriLocation = id, avoiding an unintended #/components/schemas/{id} relative fragment suffix.
    • Replaced direct instantiation via new Uri(uriLocation) with safe URI construction using Uri.TryCreate. Relative references now combine safely against BaseUri or the registered external document URI in the workspace without throwing unhandled UriFormatException or ArgumentNullException. When no base URI is available, the resolution returns null gracefully.
    • In OpenApiWorkspace.ToLocationUrl, added direct support for resolving absolute URI locations even when BaseUrl is not configured, while safely falling back to BaseUrl for relative locations via Uri.TryCreate.
  3. Regression Tests:

    • Added unit tests in OpenApiSchemaReferenceDeserializerTests (V31 and V32) covering external reference pointers without fragments (schemas/uuid.schema.json, ./schemas/uuid.schema.json, uuid.schema.json, and absolute URLs).
    • Added integration test in RelativeReferenceTests (V31) verifying that documents containing relative schema references without fragments parse and pass validation without throwing UriFormatException.
    • Added tests in OpenApiDocumentTests verifying safe reference resolution with and without workspace targets.
    • Added test in OpenApiWorkspaceTests verifying resolution of absolute URI references.

Commits

  • fix(v31): recognize external resource in schema ref without fragment (#3148)
  • fix(models): resolve relative reference URIs safely against BaseUri (#3148)

…icrosoft#3148)

Fix GetReferenceIdAndExternalResource in OpenApiV31Deserializer and OpenApiV32Deserializer so that references to external documents without a fragment (e.g., schemas/uuid.schema.json or an absolute URL) correctly populate ExternalResource and mark IsExternal = true.

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
…icrosoft#3148)

Update OpenApiDocument.ResolveReference and OpenApiWorkspace.ToLocationUrl to resolve relative references safely against BaseUri or external document URIs using Uri.TryCreate, preventing unhandled UriFormatException and ArgumentNullException when resolving relative references without fragments.

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The changes break external-component resolution and valid discriminator mappings, alter fragmentless relative references during serialization, and misinterpret root-relative workspace URLs as file URIs.

4 open issues
What changed in this PR

This PR addresses #3148 by recognizing fragmentless external schema references in the OpenAPI 3.1 and 3.2 readers and making URI resolution safer in the document and workspace layers.

Changes:

  • Classifies fragmentless schema references as external resources.
  • Uses safe URI construction for reference resolution and supports absolute workspace locations without a base URL.
  • Adds regression tests for deserialization, parsing, and workspace-backed resolution.
File Description
test/​Microsoft.OpenApi.Tests/​Workspaces/​OpenApiWorkspaceTests.cs Tests absolute URI lookup without a configured base URL.
test/​Microsoft.OpenApi.Tests/​Models/​OpenApiDocumentTests.cs Tests fragmentless relative references with and without registered targets.
test/​Microsoft.OpenApi.Readers.Tests/​V32Tests/​OpenApiSchemaReferenceDeserializerTests.cs Tests fragmentless external references in OpenAPI 3.2.
test/​Microsoft.OpenApi.Readers.Tests/​V31Tests/​RelativeReferenceTests.cs Adds a parsing regression test for a relative schema reference.
test/​Microsoft.OpenApi.Readers.Tests/​V31Tests/​OpenApiSchemaReferenceDeserializerTests.cs Tests fragmentless external references in OpenAPI 3.1.
src/​Microsoft.OpenApi/​Services/​OpenApiWorkspace.cs Updates absolute and relative URI construction for workspace lookup.
src/​Microsoft.OpenApi/​Reader/​V32/​OpenApiV32Deserializer.cs Recognizes external resources without fragments.
src/​Microsoft.OpenApi/​Reader/​V31/​OpenApiV31Deserializer.cs Recognizes external resources without fragments.
src/​Microsoft.OpenApi/​Models/​OpenApiDocument.cs Handles standalone external references and safely constructs resolution URIs.

Review effort: Balanced

string uriLocation;
var id = reference.Id;
if (!string.IsNullOrEmpty(id) && id!.Contains('/')) // this means its a URL reference
if (!string.IsNullOrEmpty(id) && (id!.Contains('/') || (reference.IsExternal && reference.ExternalResource == id))) // this means its a URL or standalone external reference

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit d0eeafa. In OpenApiDocument.ResolveReference, standalone external references are now identified by checking that the reference has no URI fragment (!referenceV3.Contains(#)), rather than testing ExternalResource == id. When external component references have colliding names (e.g., common.json#/components/schemas/common.json), the component fragment is preserved and resolves the component inside the external document instead of looking up the document URI. Added regression test ResolveReference_ExternalComponentCollidingWithDocumentNameResolvesComponent.

var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase);
string? externalResource = null;
if (isExternalResource && pointer.Contains('#'))
if (isExternalResource)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit d0eeafa. In BaseOpenApiReference.SetJsonPointerPath, authored pointers are now preserved directly without requiring # or http, correctly maintaining fragmentless relative references such as schemas/uuid.schema.json. In GetExternalReferenceV3(), standalone external references where ExternalResource == Id && !Id.Contains(#) return ExternalResource directly. Added round-trip serialization assertions in RelativeReferenceTests.cs and OpenApiSchemaReferenceDeserializerTests.cs.

var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase);
string? externalResource = null;
if (isExternalResource && pointer.Contains('#'))
if (isExternalResource)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit d0eeafa. In both OpenApiDiscriminatorDeserializer (V31 and V32), LoadMapping now detects bare schema names (via IsBareSchemaName, checking that the value is not a URI reference or file path) and constructs a local OpenApiSchemaReference(pointer, hostDocument) with ExternalResource = null. This resolves the local schema in hostDocument.Components.Schemas properly for both mapping and defaultMapping (and x-oas-default-mapping). Added regression tests in V31Tests/OpenApiDiscriminatorTests.cs and V32Tests/OpenApiDiscriminatorTests.cs.

Comment on lines +952 to +954
if (Uri.TryCreate(location, UriKind.Absolute, out var absoluteUri))
{
return new(BaseUrl, location);
return absoluteUri;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in commit d0eeafa. In OpenApiWorkspace.ToLocationUrl, we now combine with BaseUrl first via Uri.TryCreate(BaseUrl, location, out var relativeUri) before falling back to standalone absolute-URI parsing. This preserves origin resolution for root-relative locations such as /schemas/common.json against an HTTPS BaseUrl without misinterpreting them as file URIs, while retaining support for absolute URIs when no BaseUrl is configured. Added regression test OpenApiWorkspacesResolvesRootRelativeUriAgainstBaseUrl.

… mappings, and workspace URLs (microsoft#3148)

- Preserve component fragments in OpenApiDocument.ResolveReference when external resource and component names collide (e.g. common.json#/components/schemas/common.json).
- Preserve authored fragmentless relative pointers in BaseOpenApiReference.SetJsonPointerPath and GetExternalReferenceV3 to maintain exact round trips during serialization.
- Preserve bare schema names in OpenApiDiscriminatorDeserializer.LoadMapping so local schemas in discriminator mapping and defaultMapping resolve properly.
- Resolve root-relative locations against BaseUrl in OpenApiWorkspace.ToLocationUrl before fallback to absolute URI parsing.
- Catch loader errors gracefully in OpenApiWorkspaceLoader to report diagnostics without aborting document resolution.
- Add regression unit tests for colliding external references, discriminator bare schema mappings, workspace root-relative URI resolution, and round-trip serialization.

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
@hardikkaurani

Hardik K (hardikkaurani) commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi Vincent Biret (@baywet),

I've pushed an update addressing all Copilot review findings in commit [d0eeafad](d0eeafa).

Key updates:

  • External Resource & Component Name Collisions: Updated OpenApiDocument.ResolveReference to identify standalone external references using the absence of a URI fragment, preserving component fragments when resource and component names collide.
  • Relative Reference Preservation: Updated SetJsonPointerPath to preserve authored fragmentless relative references and added round-trip serialization regression tests.
  • Discriminator Mapping: Preserved local schema-name handling in OpenAPI 3.1 and 3.2, ensuring bare schema names resolve to local components.
  • Workspace URI Resolution: Updated ToLocationUrl to resolve locations against BaseUrl before attempting absolute URI parsing, preserving root-relative URL behavior.

Added regression tests covering all four scenarios.

Thank you for the detailed feedback. Whenever convenient, please take another look!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UriFormatException when resolving a relative $ref without a fragment in an OpenAPI 3.1 Schema Object

2 participants