swagger-api / swagger-api/swagger-parser

[Bug]: OAS 3.1 dereferencer crashes on schemas with + , loses / during resolution

Open
#2,331 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
867
Forks
560
Avg merge
2d 21h
Merged PRs (30d)
7

Description

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:

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

  2. 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":

  1. baseURI becomes https://example.com/schemas/Concrete
  2. resolve("#/components/schemas/Template", baseURI) yields https://example.com/schemas/Concrete#/components/schemas/Template
  3. The parser looks for JSON Pointer /components/schemas/Template inside the Concrete schema's JSON tree
  4. 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

  1. #/components/schemas/... refs should always resolve against the document root, even when the enclosing schema has $id.
  2. $dynamicRef, $dynamicAnchor, and $defs should survive the parse + dereference cycle so that downstream tooling can access them.
  3. $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): https://github.com/OpenAPITools/openapi-generator/issues/13087.

Proposed Fix Scope

This issue is scoped to parser correctness and data preservation only:

  1. avoid crashes for valid OAS 3.1 documents using $id + document-level $ref
  2. preserve $dynamicRef, $dynamicAnchor, and $defs through parse + dereference
  3. 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

  • #2266 — External schema resolution broken in OpenAPI 3.1 (related $id + $ref interaction)
  • #2201 — Ref inside of Items are not resolved (OpenAPI 3.1)

This issue was drafted with assistance from AI tooling. The submitter is responsible for reviewing and validating the contents before submission.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with OpenAPIDereferencer31 and ReferenceVisitor, especially resolveSchemaRef(), findAnchor(), mergeSchemas(), and traverseSchema(). Reproduce both cases with the supplied YAML and inspect getJsonSchema() handling of the fields. Done means document-level refs no longer crash and $dynamicRef, $dynamicAnchor, and traversed $defs survive parsing and dereferencing.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi
Domain
api
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.