swagger-api / swagger-api/apidom

Add $dynamicRef dereference support for OpenAPI 3.1 and 3.2 strategies

Open
#5,176 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
100
Forks
26
Avg merge
14h 36m
Merged PRs (30d)
22

Description

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

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 by comparing the existing $ref handling in util.ts and the OpenAPI3_1DereferenceVisitor and OpenAPI3_2DereferenceVisitor, then inspect selectors/$dynamicAnchor.ts in both strategies and the SchemaElement visitor entry point. Done means internal, external, fallback, recursive, boolean-schema, and circular-reference cases pass for both OpenAPI versions, with the referenced test fixtures and metadata behavior covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
openapi, typescript
Domain
api, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.