Skip to content

Add $dynamicRef dereference support for OpenAPI 3.1 and 3.2 strategies #5176

@aqeelat

Description

@aqeelat

Background

Issue #378 requested $dynamicRef / $dynamicAnchor dereference support for the OpenAPI 3.1 namespace in 2021. It was closed in November 2025 without a dedicated implementation. Issue #306 is the parent JSON Schema 2020-12 dereferencing epic.

ApiDOM already parses $dynamicRef and $dynamicAnchor into the element tree, but the OpenAPI 3.1 and 3.2 dereference strategies only dereference $ref. Schemas that rely on dynamic references currently pass through unresolved.

Ecosystem context

The openapi-dynamicref-adoption-tracker repository tracks $dynamicRef / $dynamicAnchor support across the OpenAPI tooling ecosystem (SDK generators, parsers, validators). It provides:

  • Validator-backed fixtures for recursive trees, nested generics, and non-identifier schema keys
  • A compatibility matrix covering 9 SDK generators across the TypeScript ecosystem
  • A state-of-the-union snapshot with per-fixture AJV / Hyperjump validation results
  • An implementation guide for adding $dynamicRef support to generators and parsers
  • A combined showcase fixture (petstore-dynamicref-showcase.yaml) exercising all $dynamicRef patterns in a realistic API

The initial SDK snapshot confirmed: no tested tool preserves $dynamicRef type fidelity. Generators either fail to parse specs containing $dynamicAnchor, emit unknown/any/Object for dynamic ref slots, or materialize generic/template fixtures as duplicate concrete types. This PR adds the parser-level dereferencing that downstream tools need.

Motivation

Downstream tools that consume ApiDOM dereferenced output should be able to work with OpenAPI 3.1/3.2 schemas that use JSON Schema 2020-12 dynamic references for recursive schemas and generic schema templates.

Proposed stages

Each stage corresponds to a single commit in PR #5177. They can be split into separate PRs if the maintainer prefers smaller reviews.

Stage 1: Add $dynamicAnchor selectors and error types

Closed by 175cccd

  • Add selectors/$dynamicAnchor.ts to both openapi-3-1 and openapi-3-2 dereference strategies
    • isDynamicAnchor — validates anchor syntax (^[A-Za-z_][A-Za-z_0-9.-]*$)
    • uriToDynamicAnchor — extracts anchor token from URI fragment
    • evaluate — finds the schema element with a matching $dynamicAnchor value via apidom-core's find
    • parse — validates anchor format, throws InvalidJsonSchema$dynamicAnchorError on invalid input
  • Add error class hierarchy:
    • JsonSchema$dynamicAnchorError (base, extends ApiDOMError)
    • EvaluationJsonSchema$dynamicAnchorError (evaluation failure)
    • InvalidJsonSchema$dynamicAnchorError (syntax validation failure)

Stage 2: Add $dynamicRef dereferencing in both visitors

Closed by 1052435

  • Add resolveSchema$dynamicRefField to util.ts in both strategies — computes the base URI for a $dynamicRef by walking ancestorsSchemaIdentifiers, matching the existing resolveSchema$refField pattern
  • Add resolveSchema$dynamicRef method to both OpenAPI3_1DereferenceVisitor and OpenAPI3_2DereferenceVisitor
    • Walks the dynamic scope (ancestor lineage, innermost-first) looking for a schema with a matching $dynamicAnchor
    • Falls back to document-level resolution when no ancestor in dynamic scope has a matching anchor — resolves the URI, loads the target resource, searches for the anchor or evaluates the JSON Pointer
    • Uses the same merge/transclusion semantics as $ref: referencing-element properties are merged on top of the transcluded schema, $dynamicRef keyword is removed from output
    • Preserves all dereference metadata: ref-fields ($dynamicRef, $dynamicRefBaseURI), ref-origin, ref-referencing-element-id
    • Handles circular references identically to $ref: max-depth checks, circular: 'error' | 'replace' | 'ignore'
    • Supports boolean JSON schemas
    • Supports external $dynamicRef — cross-file resolution with resolve.internal/resolve.external and skipNestedExternal options
    • $ref takes precedence — if a schema has both $ref and $dynamicRef, existing $ref handling runs; $dynamicRef only activates when $ref is absent
  • Update SchemaElement visitor entry point to dispatch to resolveSchema$dynamicRef when the schema has $dynamicRef but not $ref
  • Update getNestedVisitorOptions to handle $dynamicRef in skipNestedExternal logic

Stage 3: Add test coverage for internal, external, fallback, and recursive $dynamicRef

Closed by f085ce8

  • Add test fixtures and assertions for both OAS 3.1 and 3.2:
    • $dynamicRef-internal: $dynamicRef points to a $dynamicAnchor in a sibling schema within the same document — verifies transclusion
    • $dynamicRef-external: $dynamicRef points to a $dynamicAnchor in an external file — verifies cross-file resolution (with skipNestedExternal)
    • $dynamicRef-fallback: $dynamicRef has no ancestor override — verifies fallback to document-level $dynamicAnchor search
    • $dynamicRef-recursive: $dynamicRef creates a self-referential tree — verifies circularity detection (child items are the same element reference as parent)

Stage 4: Add petstore dynamic-ref showcase fixture

Closed by a7efa29

  • Add the full petstore dynamic-ref showcase fixture from the openapi-dynamicref-adoption-tracker repo as a test fixture for both OAS 3.1 and 3.2
  • Exercises all $dynamicRef patterns in a realistic API:
    • Generic response envelope (ApiEnvelopeTemplate with $dynamicAnchor: dataType)
    • Generic pagination (PaginatedTemplate with $dynamicAnchor: itemType)
    • Concrete pagination bindings (PaginatedPetItems, PaginatedOwnerItems)
    • Recursive category tree (BaseSpeciesCategory / LocalizedSpeciesCategory with $dynamicAnchor: speciesCategory)
    • Multi-parameter generic template (ShelterFolderTemplate with folderType + resourceType)
    • Typed request/response bodies

Expected outcome

Dereferencing should:

  • Resolve $dynamicRef by walking the dynamic scope (ancestor lineage, innermost-first) for a matching $dynamicAnchor
  • Fall back to document-level anchor search when no dynamic scope match is found
  • Remove the $dynamicRef keyword from the output
  • Transclude the matching schema using the same merge semantics as $ref
  • Detect and handle circular dynamic references identically to circular $ref
  • Apply to both OpenAPI 3.1 and 3.2 dereference strategies

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions