fix: preserve JSON Schema 2020-12 keyword siblings on $ref schemas for OAS 3.1+#2896
Conversation
…r OAS 3.1+ OpenApiV31Deserializer.LoadSchema short-circuits on $ref before ParseMap, so sibling keywords ($defs, $dynamicAnchor, $dynamicRef, $id, $anchor, $vocabulary, $comment) were never parsed into the object model. This made Pattern B (generic template + binding) unimplementable for any tool built on Microsoft.OpenApi. The fix mirrors the microsoft#2369 annotation-sibling pattern across four coordinated changes: - Parser extraction in SetAdditional31MetadataFromMapNode (scalars + $vocabulary) and LoadSchema ($defs, which needs LoadSchema for nested schema materialization) - Storage: 7 new properties on JsonSchemaReference - Accessor overrides on OpenApiSchemaReference (Reference.X ?? Target?.X) - Serialization in SerializeAdditionalV3XProperties Version-safe by call-site separation: SetAdditional31MetadataFromMapNode is only reachable from V31/V32 LoadSchema, never V3. Ref: microsoft#2895
Switch from GetPropertyValueFromNode(...) ?? X to the if (!string.IsNullOrEmpty(...)) pattern used by the existing Title/annotation extraction, for reviewer consistency. Also add test for the allOf-based binding variant where $defs sits inside allOf[0] and the nested schema has $ref + $dynamicAnchor (the pattern from the blocker analysis). Ref: microsoft#2895
Add $schema dialect URI as a sibling override on JsonSchemaReference, matching the pattern used for the other JSON Schema 2020-12 keywords. Also fix the $defs parsing loop in V31/V32 LoadSchema to push/pop the parsing context location stack (context.StartObject/EndObject) around each LoadSchema call, mirroring JsonNodeHelper.CreateMap. Without this, nested schemas inside a reference's $defs get incorrect nodeLocation values, breaking relative $ref resolution and source-pointer diagnostics. Adds a scalar round-trip test covering $id, $schema, $comment, $anchor, $dynamicRef serialization. Ref: microsoft#2895
Mirrors the 6 V31 sibling preservation tests in V32Tests, using SerializeAsV32 for the round-trip tests. Parse tests are identical since both versions share the same LoadSchema + SetAdditional31MetadataFromMapNode path. Ref: microsoft#2895
An empty $defs: {} or $vocabulary: {} sibling would assign an empty
collection to the reference, blocking fallthrough to Target via the
?? coalescing getter. Only assign when the collection has entries.
Ref: microsoft#2895
|
@microsoft-github-policy-service agree |
…bulary round-trip tests
- Empty $defs: {} / $vocabulary: {} must fall through to Target
(guards the .Count > 0 fix in commit 4666f2c)
- 3.0 document with $ref + siblings must drop siblings per spec
(guards the version-safety guarantee)
- $vocabulary round-trip (parse -> serialize -> parse)
Ref: microsoft#2895
…nstructor Achieves 100% diff coverage on all changed files. Ref: microsoft#2895
There was a problem hiding this comment.
Pull request overview
This PR fixes OpenAPI 3.1+ schema $ref handling so JSON Schema 2020-12 keyword siblings (notably $defs, $dynamicAnchor, $dynamicRef, $id, $schema, $anchor, $vocabulary, $comment) are preserved on OpenApiSchemaReference instances during parsing and are emitted during serialization, enabling downstream tooling to implement dynamic-scope patterns.
Changes:
- Extend
JsonSchemaReferenceandOpenApiSchemaReferenceto store and surface JSON Schema 2020-12 keyword siblings on$refschemas (with reference-first accessor precedence). - Update V3.1/V3.2 schema deserializers to parse
$defssiblings even when$refshort-circuits. - Add V3.1 and V3.2 reader/round-trip tests covering parsing, serialization, and edge cases (including empty sibling collections and 3.0 safety).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs | Adds V3.1 parsing/serialization tests ensuring $ref keyword siblings are preserved and 3.0 behavior remains unchanged. |
| test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs | Adds V3.2 parsing/serialization tests ensuring $ref keyword siblings are preserved. |
| src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs | Parses $defs siblings for $ref schemas in the V3.1 reader path. |
| src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs | Parses $defs siblings for $ref schemas in the V3.2 reader path. |
| src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | Declares newly added JsonSchemaReference public surface area. |
| src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs | Updates getters to prefer sibling keyword values stored on the reference over the target schema. |
| src/Microsoft.OpenApi/Models/JsonSchemaReference.cs | Adds storage, parsing, copying, and serialization for JSON Schema 2020-12 keyword siblings on schema references. |
baywet
left a comment
There was a problem hiding this comment.
Thanks for the contribution!
A couple of comments to help this move forward
- Replace OpenApiSpecVersion branching with Action<IOpenApiWriter, IOpenApiSerializable> callback for future-proof serialization - Add $defs context segment + try/catch error handling in V31/V32 $defs parsing (mirrors ParseField error pattern) - Convert all FluentAssertions calls to Assert.* per repo convention Ref: microsoft#2896
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
…s easier Signed-off-by: Vincent Biret <vibiret@microsoft.com>
baywet
left a comment
There was a problem hiding this comment.
Thank you for making the changes!
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
|
@baywet Happy to help. |
|
@aqeelat sure thing! I'm running the performance tests to update the results anyway. Plus we now need a third approver since I made changes. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@baywet thank you for the fast turnaround. When do you think this will be released? Is it possible to get 3.7.1 today? |
fix: preserve JSON Schema 2020-12 keyword siblings on $ref schemas for OAS 3.1+ (#2896)
Problem
OpenApiV31Deserializer.LoadSchemashort-circuits on$refbeforeParseMap, so JSON Schema 2020-12 keyword siblings ($defs,$dynamicAnchor,$dynamicRef,$id,$schema,$anchor,$vocabulary,$comment) were never parsed into the object model. This made Pattern B (generic template +$dynamicRefbinding via sibling$defs) unimplementable for any tool built onMicrosoft.OpenApi.Fixes #2895.
Solution
Mirrors the #2369 annotation-sibling pattern across four coordinated changes:
SetAdditional31MetadataFromMapNodeextracts scalar/collection siblings;$defsis parsed inLoadSchema(needsLoadSchemafor nested schema materialization).JsonSchemaReference($idstored asSchemaIdto avoid collision withBaseOpenApiReference.Id).OpenApiSchemaReferencegetters changed fromTarget?.XtoReference.X ?? Target?.X.SerializeAdditionalV3XPropertiesemits the new siblings alongside$ref.Version-safe by call-site separation:
SetAdditional31MetadataFromMapNodeis only reachable from V31/V32LoadSchema, never V3. All 1639 existing tests pass unchanged.Scope
Intentionally narrow — only JSON Schema 2020-12 structural keywords (
$-prefixed). Application keywords (type,properties,required, etc.) still delegate toTarget. Full sibling compliance is a separate concern with backward-compat implications.Empty
$defs: {}/$vocabulary: {}siblings do not suppress the target's values (collection assigned only when non-empty).Test coverage
12 new tests across V31 and V32:
$dynamicAnchor+$defs), scalar siblings ($id,$schema,$comment,$anchor,$dynamicRef),$vocabulary,allOf-binding variant