feat: parse and preserve $dynamicRef (JSON Schema 2020-12)#501
Conversation
…nd-trip Address review feedback on mattpolzin#501: - Add test verifying the dynamic scope travels across a $ref boundary (anchor in Outer's $defs resolved by a $dynamicRef inside a referenced Inner component) -- the key mechanism added by the .reference refactor. - Add test verifying a plain-fragment $ref ("#foo") round-trips verbatim instead of being rewritten with a slash. - Document the $ref plain-fragment round-trip behavior change in the v7 migration guide.
Add parsing and round-trip serialization for the JSON Schema 2020-12 $dynamicRef / $dynamicAnchor keywords (spec §7.7), adopted by OpenAPI 3.1.x as its schema dialect. $dynamicAnchor parsing landed in mattpolzin#360, but $dynamicRef was previously dropped: schemas whose only attribute was $dynamicRef decoded as empty fragments with a 'Found nothing but unsupported attributes' warning. With this change the keyword is parsed and preserved, unblocking downstream tooling (e.g. apple/swift-openapi-generator#547) to observe it. Scope (parse/preserve only): - New JSONDynamicReference type wrapping JSONReference<JSONSchema> with $dynamicRef encode/decode. - New .anchor(String) case on JSONReference.InternalReference so plain fragment references like '#category' parse and round-trip. - New .dynamicReference case on JSONSchema.Schema and DereferencedJSONSchema, threaded through every exhaustive switch and the schema transformations (factory, encode/decode, optional/required/ nullable, with(...), fragment combining, external dereferencing). - Decoder recognizes $dynamicRef before the unsupported-attributes fallthrough. - Local dereferencing preserves .dynamicReference unchanged (dynamic-scope resolution is a follow-up, tracked in mattpolzin#359). This is a source-breaking change (new public enum cases). The v7 migration guide is added documenting it. Part of mattpolzin#359.
ce0b317 to
73c99fb
Compare
|
Two quick design questions before a deeper review — both could change the shape of this PR, so figured I'd surface them up front. 1. Target branch. This adds new cases to public enums ( 2. AST shape. I went with a dedicated The rest (parse / decode / encode, the decoder no longer warning on |
Part of #359.
Summary
Adds parsing and round-trip serialization for the JSON Schema 2020-12
$dynamicRef/$dynamicAnchorkeywords (spec §7.7), which OpenAPI 3.1.x adopts as its schema dialect.$dynamicAnchorparsing landed in #360, but$dynamicRefwas dropped: schemas whose only attribute was$dynamicRefdecoded as empty fragments with a "Found nothing but unsupported attributes" warning. This PR makes the keyword parse and round-trip.Changes
JSONDynamicReferencetype wrappingJSONReference<JSONSchema>with$dynamicRefencode/decode..anchor(String)case onJSONReference.InternalReferenceso plain fragment references like#categoryparse and round-trip..dynamicReferencecase onJSONSchema.SchemaandDereferencedJSONSchema, threaded through every exhaustive switch and the schema transformations (factory, encode/decode, optional/required/nullable,with(...), fragment combining, external dereferencing).$dynamicRefbefore the unsupported-attributes fallthrough..dynamicReferenceunchanged — no resolution yet (that's the follow-up).Behavior
$dynamicRef-only schema → decodes as.dynamicReference, no warning (previously an empty.fragment+ "unsupported attributes").$dynamicRefsurvives encode/decode round-trips, including inside real document trees (recursive patterns, generics).dereferenced(in:)preserves.dynamicReferenceas-is.Scope decision
This PR is intentionally scoped to parse/preserve/serialize, matching what you sketched on
feature/359/dynamic-refand the "parse the references/anchors but not support them more deeply" approach. The dynamic-scope resolution (inlining non-recursive targets, breaking cycles on recursive ones) is a separate concern that I'll propose in a follow-up — it touches dereferencing semantics you flagged as subtle, so it seems best to review independently.Testing
JSONSchemaDynamicReferenceTests: decode (anchor/component), the no-"unsupported-attributes" regression, encode round-trip, a full document round-trip, accessors/transformations,$refplain-fragment round-trip, and a passthrough-survival dereferencing test.recursive-category-tree.yaml—$dynamicRefnow survives decode (previously stripped).Breaking change
This adds new cases to public enums (
JSONSchema.Schema,DereferencedJSONSchema,JSONReference.InternalReference), so it's source-breaking for exhaustive switches. The v7 migration guide is added documenting it. Happy to retarget torelease/7_0if you'd prefer breaking changes land there rather thanmain.This PR was drafted with assistance from AI tooling. The submitter has reviewed and validated the contents prior to submission.