Skip to content

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

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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions