Skip to content

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

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

Hardik K (hardikkaurani) wants to merge 5 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

… 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!

@baywet

Copy link
Copy Markdown
Member

Hardik K (@hardikkaurani) this pull request is conflicting, please address the conflicts.

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.

Comment on lines +257 to +260
if (string.Equals(ExternalResource, Id, StringComparison.Ordinal) && !Id.Contains("#"))
{
return ExternalResource;
}

@hardikkaurani Hardik K (hardikkaurani) Nov 14, 2026 •

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 fb6b8eb8.

  • Added an internal IsStandalone flag to BaseOpenApiReference to explicitly distinguish standalone document references from component references, eliminating the equality check in GetExternalReferenceV3 that caused fragments to be dropped when component names matched.
  • Updated all reference readers across V3, V3.1, and V3.2, including responses, parameters, headers, callbacks, examples, links, request bodies, security schemes, and media types, to preserve authored pointers through SetJsonPointerPath.
  • Added regression tests in OpenApiDocumentTests covering colliding non-schema references and in RelativeReferenceTests verifying round-trip preservation.

}
}
if (result.Document != null)
catch (Exception ex)

@hardikkaurani Hardik K (hardikkaurani) Nov 14, 2026 •

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 fb6b8eb8.

Excluded OperationCanceledException from the catch block using an exception filter (catch (Exception ex) when (ex is not OperationCanceledException)), ensuring cancellation requests propagate correctly to the caller instead of being handled as regular errors.

Added regression tests in OpenApiWorkspaceStreamTests covering both loader-triggered and cancellation-token-triggered scenarios.

Comment on lines +78 to +80
&& !pointer.EndsWith(".json", StringComparison.OrdinalIgnoreCase)
&& !pointer.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase)
&& !pointer.EndsWith(".yml", StringComparison.OrdinalIgnoreCase);

@hardikkaurani Hardik K (hardikkaurani) Nov 14, 2026 •

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 fb6b8eb8.

Removed the filename suffix checks (.json, .yaml, .yml) from IsBareSchemaName in both V3.1 and V3.2 deserializers. Identifiers without path separators (/) or URI fragments (#) are now treated as local component names, preserving local resolution for file-suffixed and forward-declared schemas in both mapping and defaultMapping.

Added regression tests in V31Tests/OpenApiDiscriminatorTests and V32Tests/OpenApiDiscriminatorTests covering these scenarios.

…thout-fragment-uri-format

# Conflicts:
#	test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs
…cellation, and preserve file-suffixed discriminator schemas (microsoft#3148)

- Exclude OperationCanceledException from OpenApiWorkspaceLoader catch handler to preserve caller cancellation token semantics.
- Retain authored component fragments and reference pointers across non-schema reference kinds (responses, parameters, headers, callbacks, examples, links, request bodies, security schemes, media types).
- Distinguish standalone references from component references via IsStandalone on BaseOpenApiReference, preventing colliding component names from dropping their fragment.
- Preserve local schema resolution in OpenApiDiscriminatorDeserializer for bare schema names including file-suffixed (.json, .yaml, .yml) and forward-declared schemas.
- Add regression test coverage for colliding non-schema references, loader cancellation, and file-suffixed/forward-declared discriminator mappings.

Signed-off-by: Hardik K <hardikkaurani1@gmail.com>
Copilot AI balanced review requested due to automatic review settings November 14, 2026 04:59

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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@hardikkaurani

Hardik K (hardikkaurani) commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi Vincent Biret (@baywet),

I've resolved the merge conflicts with main and addressed the latest review feedback in commit fb6b8eb8:

  • Conflict Resolution: Merged upstream/main and adapted references to the updated SetJsonPointerPath signature and location handling.
  • Non-Schema Reference Preservation: Introduced an explicit IsStandalone flag on BaseOpenApiReference and updated all reference readers to preserve authored pointers, preventing fragment loss when resource and component names collide (e.g., common.json#/components/responses/common.json).
  • Loader Cancellation: Excluded OperationCanceledException from OpenApiWorkspaceLoader error handling to ensure caller cancellation tokens are respected.
  • Discriminator Mapping: Removed filename suffix checks from IsBareSchemaName in both V3.1 and V3.2, allowing file-suffixed (.json, .yaml, .yml) and forward-declared local schemas to resolve correctly.

All 2,013 unit and integration tests pass locally.

Whenever you have a moment, I'd appreciate another look. Thank you!

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

3 participants