-
-
Notifications
You must be signed in to change notification settings - Fork 7.6k
[core] keep property type when an allOf part redefines it without a type #24586
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ee73d81
90a7c32
2a9ebcb
49352ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2807,6 +2807,55 @@ public static void copyMetadata(Schema from, Schema to) { | |||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Copies metadata plus the validation and format keywords that copyMetadata does not cover. | ||||||||||||
| * Used when an allOf part only constrains an inherited property (see issue #4128), so no | ||||||||||||
| * declared keyword is lost while the type is kept. | ||||||||||||
| * | ||||||||||||
| * @param from schema to copy from | ||||||||||||
| * @param to schema to copy to | ||||||||||||
| */ | ||||||||||||
| public static void copyConstraints(Schema from, Schema to) { | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: An allOf property overlay containing Prompt for AI agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same fix in da0796a, not is excluded from the merge, old replace behavior applies. |
||||||||||||
| Map<String, Object> targetExtensions = to.getExtensions() == null ? null : new HashMap<>(to.getExtensions()); | ||||||||||||
| copyMetadata(from, to); | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Generated validation can be weakened when the incoming allOf constraint is looser than the inherited one because this merge overwrites existing bounds instead of intersecting them. Combining numeric, length, item, and property bounds using the most restrictive value would preserve the schema’s allOf semantics. Prompt for AI agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this one is out of scope here. Intersecting bounds would need constraint resolution logic, and overlay-wins is the existing convention in this area (mergeProperties, the normalizer). Before this PR the inherited bounds were dropped entirely, so this is not a regression. Happy to open a follow-up issue for intersection semantics if maintainers want it. |
||||||||||||
| // merge extensions per key instead of replacing, the source wins on conflicts | ||||||||||||
| if (targetExtensions != null && from.getExtensions() != null) { | ||||||||||||
| Map<String, Object> mergedExtensions = new HashMap<>(targetExtensions); | ||||||||||||
| mergedExtensions.putAll(from.getExtensions()); | ||||||||||||
| to.setExtensions(mergedExtensions); | ||||||||||||
| } | ||||||||||||
| if (from.getFormat() != null) { | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: A typeless allOf overlay using Prompt for AI agents
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in da0796a. Overlays carrying const keep the old replace behavior, const reads as a value redefinition rather than a constraint. |
||||||||||||
| to.setFormat(from.getFormat()); | ||||||||||||
| } | ||||||||||||
| if (from.getPattern() != null) { | ||||||||||||
| to.setPattern(from.getPattern()); | ||||||||||||
| } | ||||||||||||
| if (from.getExclusiveMaximum() != null) { | ||||||||||||
| to.setExclusiveMaximum(from.getExclusiveMaximum()); | ||||||||||||
| } | ||||||||||||
| if (from.getExclusiveMinimum() != null) { | ||||||||||||
| to.setExclusiveMinimum(from.getExclusiveMinimum()); | ||||||||||||
| } | ||||||||||||
| if (from.getExclusiveMaximumValue() != null) { | ||||||||||||
| to.setExclusiveMaximumValue(from.getExclusiveMaximumValue()); | ||||||||||||
| } | ||||||||||||
| if (from.getExclusiveMinimumValue() != null) { | ||||||||||||
| to.setExclusiveMinimumValue(from.getExclusiveMinimumValue()); | ||||||||||||
| } | ||||||||||||
| if (from.getMultipleOf() != null) { | ||||||||||||
| to.setMultipleOf(from.getMultipleOf()); | ||||||||||||
| } | ||||||||||||
| if (from.getUniqueItems() != null) { | ||||||||||||
| to.setUniqueItems(from.getUniqueItems()); | ||||||||||||
| } | ||||||||||||
| if (from.getMaxProperties() != null) { | ||||||||||||
| to.setMaxProperties(from.getMaxProperties()); | ||||||||||||
| } | ||||||||||||
| if (from.getMinProperties() != null) { | ||||||||||||
| to.setMinProperties(from.getMinProperties()); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Returns true if a schema is only metadata and not an actual type. | ||||||||||||
| * For example, a schema that only has a `description` without any `properties` or `$ref` defined. | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| openapi: 3.0.3 | ||
| info: | ||
| title: allOf nullable without type | ||
| version: 1.0.0 | ||
| paths: | ||
| /firm/{firmId}: | ||
| patch: | ||
| operationId: updateFirm | ||
| parameters: | ||
| - in: path | ||
| name: firmId | ||
| required: true | ||
| schema: | ||
| type: integer | ||
| format: int64 | ||
| requestBody: | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/UpdateFirm" | ||
| responses: | ||
| "204": | ||
| description: Updated | ||
| components: | ||
| schemas: | ||
| FirmProperties: | ||
| properties: | ||
| addressId: | ||
| type: string | ||
| x-base-marker: keep | ||
| UpdateFirm: | ||
| allOf: | ||
| - $ref: "#/components/schemas/FirmProperties" | ||
| - properties: | ||
| firmName: | ||
| type: string | ||
| nullable: true | ||
| addressId: | ||
| nullable: true | ||
| maxLength: 36 | ||
| x-overlay-marker: added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The new merge only preserves a hand-picked subset of constraints. isConstraintOnlySchema() matches any typeless schema, so a part that only overrides e.g.
maxLength,pattern,format,minimum/maximumordefaulton an inherited property enters the merge branch, but putProperty copies only nullable/description/deprecated/readOnly/writeOnly/extensions onto the clone — every other keyword is silently dropped while the type is kept. The generated model/validation will therefore be weaker than the OpenAPI spec declares (the constraint is lost instead of degraded). Consider either copying the remaining validation/format fields onto merged, or narrowing isConstraintOnlySchema to only the keywords that putProperty actually merges, so the Javadoc's 'constraints are applied' claim holds and no constraint silently disappears.Prompt for AI agents