feat(schema): add first-class $defs field + traverse all sibling keywords in SpecFilter#5207
Open
aqeelat wants to merge 2 commits into
Open
feat(schema): add first-class $defs field + traverse all sibling keywords in SpecFilter#5207aqeelat wants to merge 2 commits into
aqeelat wants to merge 2 commits into
Conversation
Adds $defs to io.swagger.v3.oas.models.media.Schema, mirroring how the other 2020-12 keywords ($dynamicAnchor, $dynamicRef, $anchor, $id, $schema, $vocabulary, patternProperties, dependentSchemas, ...) were added. $defs is the canonical location for the generic-template binding used with $dynamicRef/$dynamicAnchor and was the notable omission. - Schema: $defs field + get$defs/set$defs/$defs(Map)/add$defs accessors, gated by @OpenAPI31; included in equals/hashCode/toString - SchemaMixin: @JsonIgnore on get$defs() for V30 exclusion - V31 serializer/deserializer need no changes (auto-discovery via getters/setters, same as $dynamicAnchor etc.) Behavioral change for consumers: $defs in input JSON is now routed to the dedicated field rather than the extensions map. Producers using the extension-injection workaround continue to work (the $defs field stays null, extensions entry is still emitted via @JsonAnyGetter). Refs: swagger-api#4469
addSchemaRef previously had two gaps that caused
RemoveUnreferencedDefinitionsFilter to silently drop schemas referenced
via certain keyword containers:
1. Bug A (OAS 3.1 $ref siblings): seven keyword traversals sat after
an early return on $ref, so any sibling of $ref was skipped. With
OAS 3.1 allowing $ref siblings, schemas like
{$ref: Parent, $defs: {x: {$ref: Target}}} lost Target.
2. Bug B (missing traversals): nine JSON Schema 2020-12 keywords were
never traversed at all, $ref or not: not, prefixItems, contains,
contentSchema, unevaluatedProperties, additionalItems,
unevaluatedItems, if/else/then, dependentSchemas. A schema like
{contains: {$ref: X}} dropped X.
Restructure addSchemaRef to:
- record $ref without early-return; always traverse siblings
- traverse all 20 sibling keyword containers uniformly
- add an IdentityHashMap-backed visited-set for cycle protection
- drop the items/allOf mutual exclusivity (incidental fix: an
ArraySchema with allOf previously had its allOf entries skipped)
For OAS 3.0 documents the behavioral change is over-retention of
referenced schemas (safe failure mode for a "remove unreferenced"
filter); the worst case is a referenced schema surviving, never a
needed one being dropped.
Tests: 40 new data-driven cases via \@dataProvider (20 keywords x
2 scenarios: standalone {$ref: kw} and $ref sibling {$ref, kw}).
Refs: swagger-api#4469
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a first-class
$defsfield toio.swagger.v3.oas.models.media.Schema, mirroring how the other JSON Schema 2020-12 keywords ($dynamicAnchor,$dynamicRef,$anchor,$id,$schema,$vocabulary,patternProperties,dependentSchemas, …) were added.$defsis the canonical location for the generic-template binding used with$dynamicRef/$dynamicAnchorand was the notable omission. Also fixes two related gaps inSpecFilter.addSchemaRefthat dropped schema references.Fixes: #4469
What & why
1. First-class
$defsfield (feat(schema)commit)Schema:$defsfield +get$defs/set$defs/$defs(Map)/add$defsaccessors, gated by@OpenAPI31; included inequals/hashCode/toString.SchemaMixin:@JsonIgnoreonget$defs()for V30 exclusion (matches the existing pattern for$id,$anchor,patternProperties,dependentSchemas, etc.).$dynamicAnchor/$dynamicRef.Behavioral change for consumers:
$defsin input JSON is now routed to the dedicated field rather than the extensions map. Producers using the extension-injection workaround continue to work (the$defsfield stays null, the extensions entry is still emitted via@JsonAnyGetter). See the breaking-change analysis comment on the issue for migration impact.2.
SpecFilter.addSchemaRefrestructure (fix(filter)commit)While wiring
$defsinto the filter I uncovered two pre-existing bugs inaddSchemaRef(used byRemoveUnreferencedDefinitionsFilter):$refsiblings skipped. Seven keyword traversals sat after an early-return on$ref, so any sibling of$refwas skipped. With OAS 3.1 allowing$refsiblings, schemas like{$ref: Parent, $defs: {x: {$ref: Target}}}lostTarget.$refor not:not,prefixItems,contains,contentSchema,unevaluatedProperties,additionalItems,unevaluatedItems,if/else/then,dependentSchemas. A schema like{contains: {$ref: X}}droppedX.Restructure:
$refwithout early-return; always traverse siblings.traverseSchemaMap/traverseSchemaListhelpers.IdentityHashMap-backedvisited-set for cycle protection.if/elsechain madeitemsandallOf/anyOf/oneOfmutually exclusive — anArraySchemawithallOfhad itsallOfentries silently dropped.For OAS 3.0 documents the behavioral change is over-retention of referenced schemas (safe failure mode for a "remove unreferenced" filter); the worst case is a referenced schema surviving, never a needed one being dropped. Full sibling traversal was chosen over V31-gating for consistency with how every prior 2020-12 keyword (
patternProperties,prefixItems,dependentSchemas,$dynamicAnchor,$dynamicRef) was wired up unconditionally, and because the missing-keyword traversals (Bug B) only affect keywords that cannot appear in strict OAS 3.0 docs anyway.Type of Change
Checklist
mvn testonswagger-models+swagger-core; 69SpecFilterTestcases pass, fullswagger-coresuite green)Tests
OpenAPI3_1DeserializationTest:$defsround-trip, accumulation, equality, and the consumer-side deserialization routing change.SwaggerSerializerTest: V31-only emission + extension-coexistence characterization (legacy workaround still works; mixing field + extensions produces duplicate keys, documented).SpecFilterTest: 40 new data-driven cases via@DataProvider(20 sibling keywords × 2 scenarios — standalone{$ref: kw}and$refsibling{$ref, kw}).Downstream follow-ups (out of scope)
$defsfrom the extensions map; it should switch toget$defs()once this ships.SchemaDefinitionUtils.setSchemaDefs()helper behind a cached reflection check forset$defs— it auto-switches to the native method once available.Additional Context
Detailed motivation, empirical evidence, and downstream layering context are in the issue thread (#4469). The two commits are split deliberately: reviewers who only care about the new field can focus on the first; the filter restructure is separable.