A $dynamicRef fragment is URI text, so #my%2Danchor names the anchor my-anchor. The
anchor index compares the raw fragment against declared $dynamicAnchor names instead, so a
spec-correct escape matches nothing: the reference is reported irreducible and kept verbatim
under Unmodeled rather than expanded to the type it names.
Reproduction
openapi: 3.1.0
info: {title: t, version: "1"}
paths: {}
components:
schemas:
A:
type: object
properties:
x: {$dynamicRef: "#my%2Danchor"}
B: {$dynamicAnchor: my-anchor, type: string}
Emitted:
openapi/degraded-construct: $dynamicRef was not expanded because no $dynamicAnchor
"my%2Danchor" is declared in this document; it is kept verbatim under Unmodeled
Spelling the same reference #my-anchor expands it to t/openapi/components/schemas/B.
Root cause
dynamicFragment (compilers/openapi/internal/schema/schema.go) cuts the leading # and
returns the remainder as the anchor name, never percent-decoding it. soleAnchorSite then looks
that raw text up in the $dynamicAnchor index, which is keyed by the names as declared.
Why this is separate from #40
#40 covers $ref pointers, where the fix is to read the fragment the way the library that
actually resolves the reference reads it — disagreeing with the resolver is what made it a bug.
Nothing external resolves $dynamicRef: this compiler owns the anchor index and the match, so
what the decoding should be is a decision about our own behavior rather than one taken by
matching a dependency. Worth settling on its own terms, including whether $anchor names should
be decoded on the declaration side too.
A
$dynamicReffragment is URI text, so#my%2Danchornames the anchormy-anchor. Theanchor index compares the raw fragment against declared
$dynamicAnchornames instead, so aspec-correct escape matches nothing: the reference is reported irreducible and kept verbatim
under
Unmodeledrather than expanded to the type it names.Reproduction
Emitted:
Spelling the same reference
#my-anchorexpands it tot/openapi/components/schemas/B.Root cause
dynamicFragment(compilers/openapi/internal/schema/schema.go) cuts the leading#andreturns the remainder as the anchor name, never percent-decoding it.
soleAnchorSitethen looksthat raw text up in the
$dynamicAnchorindex, which is keyed by the names as declared.Why this is separate from #40
#40 covers
$refpointers, where the fix is to read the fragment the way the library thatactually resolves the reference reads it — disagreeing with the resolver is what made it a bug.
Nothing external resolves
$dynamicRef: this compiler owns the anchor index and the match, sowhat the decoding should be is a decision about our own behavior rather than one taken by
matching a dependency. Worth settling on its own terms, including whether
$anchornames shouldbe decoded on the declaration side too.