Description
The OAS 3.1 dereferencer (OpenAPIDereferencer31 + ReferenceVisitor) has two related issues that together prevent parsing any OpenAPI 3.1 spec that uses $dynamicRef / $dynamicAnchor:
-
Hard crash: When a schema in components/schemas has both $id and $ref, the dereferencer resolves the $ref against the $id URI instead of the document root. For refs like #/components/schemas/..., this causes a RuntimeException: Could not find ....
-
Data loss: $dynamicRef, $dynamicAnchor, and $defs are deserialized correctly but are not preserved through the resolution cycle. mergeSchemas() does not copy these fields, $defs is not traversed, and findAnchor() ignores $dynamicAnchor.
Environment
- swagger-parser version: 2.1.41 (latest release)
- OpenAPI version: 3.1.x
Reproduction
Case 1: $id + $ref crash
openapi: 3.1.0
info:
title: Test
version: 1.0.0
paths: {}
components:
schemas:
Template:
type: object
properties:
items:
type: array
Concrete:
$id: "https://example.com/schemas/Concrete"
$ref: "#/components/schemas/Template"
ParseOptions options = new ParseOptions();
options.setResolve(true);
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("spec.yaml", null, options);
// result.getMessages() contains:
// "Could not find /components/schemas/Template in contents of #/components/schemas/Template"
Case 2: $dynamicRef / $dynamicAnchor lost
openapi: 3.1.0
info:
title: Test
version: 1.0.0
paths: {}
components:
schemas:
Node:
type: object
properties:
children:
type: array
items:
$dynamicRef: "#node"
$defs:
node:
$dynamicAnchor: node
After parsing with setResolve(true), $dynamicRef, $dynamicAnchor, and the $defs contents may not survive the dereference cycle.
Full reproduction specs
Minimal validator-backed OpenAPI 3.1 fixtures demonstrating both issues are available at:
https://github.com/aqeelat/openapi-dynamicref-adoption-tracker
Likely Cause Analysis
Bug 1: $id appears to hijack $ref resolution
In ReferenceVisitor.resolveSchemaRef() (line 230-279), the base URI is computed by chaining inheritedIds (the $id stack) against the current reference URI:
String baseURI = this.reference.getUri();
for (String id: inheritedIds) {
baseURI = ReferenceUtils.resolve(ReferenceUtils.toBaseURI(id), baseURI);
baseURI = ReferenceUtils.toBaseURI(baseURI);
}
baseURI = ReferenceUtils.resolve(ref, baseURI);
When Concrete has $id: "https://example.com/schemas/Concrete" and $ref: "#/components/schemas/Template":
baseURI becomes https://example.com/schemas/Concrete
resolve("#/components/schemas/Template", baseURI) yields https://example.com/schemas/Concrete#/components/schemas/Template
- The parser looks for JSON Pointer
/components/schemas/Template inside the Concrete schema's JSON tree
Concrete has no components property → crash
In pure JSON Schema, $id changing the base URI is correct. But in an OpenAPI document, #/components/schemas/... is a document-level reference that should resolve against the document root regardless of any schema's $id.
Bug 2: Data is not fully preserved during resolution
| Field |
Deserialized? |
Preserved by mergeSchemas()? |
Traversed? |
$dynamicRef |
Yes (getJsonSchema() line 4251) |
No |
N/A (not a container) |
$dynamicAnchor |
Yes (getJsonSchema() line 4246) |
No |
N/A (not a container) |
$defs |
Not mapped to a dedicated Schema field (retained as raw extension data) |
N/A |
No (traverseSchema() does not visit $defs) |
Additionally, findAnchor() (line 281-308) only searches for $anchor, not $dynamicAnchor.
Expected Behavior
#/components/schemas/... refs should always resolve against the document root, even when the enclosing schema has $id.
$dynamicRef, $dynamicAnchor, and $defs should survive the parse + dereference cycle so that downstream tooling can access them.
$defs sub-schemas should be traversed so that any $ref or $dynamicAnchor inside them is available in the parsed output.
Impact
This is reproducible in swagger-parser 2.1.41 with the fixtures above and affects OpenAPI 3.1 specs that use JSON Schema 2020-12 features for dynamic polymorphism — specifically the $dynamicRef / $dynamicAnchor pattern for recursive schemas, generic wrappers, and template types as described in:
Related downstream report (OpenAPI Generator, which depends on swagger-parser): OpenAPITools/openapi-generator#13087.
Proposed Fix Scope
This issue is scoped to parser correctness and data preservation only:
- avoid crashes for valid OAS 3.1 documents using
$id + document-level $ref
- preserve
$dynamicRef, $dynamicAnchor, and $defs through parse + dereference
- traverse
$defs sub-schemas so nested references/anchors remain available
It does not request implementation of full JSON Schema 2020-12 dynamic scope resolution.
Related Issues
This issue was drafted with assistance from AI tooling. The submitter is responsible for reviewing and validating the contents before submission.
Description
The OAS 3.1 dereferencer (
OpenAPIDereferencer31+ReferenceVisitor) has two related issues that together prevent parsing any OpenAPI 3.1 spec that uses$dynamicRef/$dynamicAnchor:Hard crash: When a schema in
components/schemashas both$idand$ref, the dereferencer resolves the$refagainst the$idURI instead of the document root. For refs like#/components/schemas/..., this causes aRuntimeException: Could not find ....Data loss:
$dynamicRef,$dynamicAnchor, and$defsare deserialized correctly but are not preserved through the resolution cycle.mergeSchemas()does not copy these fields,$defsis not traversed, andfindAnchor()ignores$dynamicAnchor.Environment
Reproduction
Case 1:
$id+$refcrashCase 2:
$dynamicRef/$dynamicAnchorlostAfter parsing with
setResolve(true),$dynamicRef,$dynamicAnchor, and the$defscontents may not survive the dereference cycle.Full reproduction specs
Minimal validator-backed OpenAPI 3.1 fixtures demonstrating both issues are available at:
https://github.com/aqeelat/openapi-dynamicref-adoption-tracker
Likely Cause Analysis
Bug 1:
$idappears to hijack$refresolutionIn
ReferenceVisitor.resolveSchemaRef()(line 230-279), the base URI is computed by chaininginheritedIds(the$idstack) against the current reference URI:When
Concretehas$id: "https://example.com/schemas/Concrete"and$ref: "#/components/schemas/Template":baseURIbecomeshttps://example.com/schemas/Concreteresolve("#/components/schemas/Template", baseURI)yieldshttps://example.com/schemas/Concrete#/components/schemas/Template/components/schemas/Templateinside theConcreteschema's JSON treeConcretehas nocomponentsproperty → crashIn pure JSON Schema,
$idchanging the base URI is correct. But in an OpenAPI document,#/components/schemas/...is a document-level reference that should resolve against the document root regardless of any schema's$id.Bug 2: Data is not fully preserved during resolution
mergeSchemas()?$dynamicRefgetJsonSchema()line 4251)$dynamicAnchorgetJsonSchema()line 4246)$defstraverseSchema()does not visit$defs)Additionally,
findAnchor()(line 281-308) only searches for$anchor, not$dynamicAnchor.Expected Behavior
#/components/schemas/...refs should always resolve against the document root, even when the enclosing schema has$id.$dynamicRef,$dynamicAnchor, and$defsshould survive the parse + dereference cycle so that downstream tooling can access them.$defssub-schemas should be traversed so that any$refor$dynamicAnchorinside them is available in the parsed output.Impact
This is reproducible in swagger-parser
2.1.41with the fixtures above and affects OpenAPI 3.1 specs that use JSON Schema 2020-12 features for dynamic polymorphism — specifically the$dynamicRef/$dynamicAnchorpattern for recursive schemas, generic wrappers, and template types as described in:Related downstream report (OpenAPI Generator, which depends on swagger-parser): OpenAPITools/openapi-generator#13087.
Proposed Fix Scope
This issue is scoped to parser correctness and data preservation only:
$id+ document-level$ref$dynamicRef,$dynamicAnchor, and$defsthrough parse + dereference$defssub-schemas so nested references/anchors remain availableIt does not request implementation of full JSON Schema 2020-12 dynamic scope resolution.
Related Issues
$id+$refinteraction)This issue was drafted with assistance from AI tooling. The submitter is responsible for reviewing and validating the contents before submission.