Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions typify-impl/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,11 +464,15 @@ impl TypeSpace {
extensions: _,
} => self.convert_unknown_enum(type_name, original_schema, metadata, enum_values),

// Subschemas
// Subschemas with no body validation and either no type or a
// single type. A multi-type (`Vec`) instance_type is deliberately
// excluded so that it flows through the merge arm below, which
// folds the type union into each subschema branch. Preserving the
// earlier behaviour for `None` / `Single` keeps existing tolerant
// handling of schemas whose outer type may conflict with a branch.
Comment on lines +467 to +472
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Subschemas with no body validation and either no type or a
// single type. A multi-type (`Vec`) instance_type is deliberately
// excluded so that it flows through the merge arm below, which
// folds the type union into each subschema branch. Preserving the
// earlier behaviour for `None` / `Single` keeps existing tolerant
// handling of schemas whose outer type may conflict with a branch.
// Subschemas with no additional validation and either no type or a
// single type. A multi-type (`Vec`) instance_type is deliberately
// excluded so that it flows through the merge arm below.

SchemaObject {
metadata,
// TODO we probably shouldn't ignore this...
instance_type: _,
instance_type: None | Some(SingleOrVec::Single(_)),
format: None,
enum_values: None,
const_value: None,
Expand Down
89 changes: 89 additions & 0 deletions typify/tests/schemas/type-array-with-subschemas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$comment": "Regression coverage for issue #954: schemas with a multi-type `type: [...]` array on the same object as `oneOf` / `anyOf` / `allOf` / `not` previously discarded the `type` constraint (TODO at convert.rs wildcarded `instance_type`). Single-type + subschema cases must still pass through the earlier arm unchanged.",
"definitions": {
"TypeArrayOneOfItems": {
"$comment": "Canonical issue #954 shape. Each oneOf branch only constrains `items`, so the type union must be folded into every branch rather than dropped.",
"type": [ "string", "number", "boolean", "array" ],
"oneOf": [
{ "items": { "type": "string" } },
{ "items": { "type": "number" } },
{ "items": { "type": "boolean" } }
]
},
"TypeArrayAnyOfItems": {
"$comment": "Same shape as TypeArrayOneOfItems but using anyOf. anyOf travels through try_merge_with_each_subschema on a sibling path from oneOf; it should fold the type union the same way.",
"type": [ "string", "number", "array" ],
"anyOf": [
{ "items": { "type": "string" } },
{ "items": { "type": "number" } }
]
},
"TypeArrayAllOfRefinement": {
"$comment": "allOf is folded pairwise into the parent rather than producing branches. The type union must survive and the array-only constraints should apply when the Array variant is selected.",
"type": [ "string", "array" ],
"allOf": [
{ "items": { "type": "string" } },
{ "minItems": 1 }
]
},
"TypeArrayNotExclusion": {
"$comment": "not: object is redundant when the outer type union excludes object, but merging must not drop the type union when the not branch is applied.",
"type": [ "string", "number", "array" ],
"not": { "type": "object" }
},
"SingleTypeOneOfArrayBranch": {
"$comment": "Regression guard (rust-collisions pattern). Outer singleton type + oneOf where one branch has a conflicting explicit type. This must continue to pass through the earlier arm (no merge), otherwise the array branch becomes unsatisfiable and is silently dropped.",
"type": "object",
"oneOf": [
{
"type": "object",
"properties": { "kind": { "type": "string" } },
"required": [ "kind" ]
},
{
"type": "array",
"items": { "type": "string" },
"minItems": 2,
"maxItems": 2
}
]
},
"TypeArrayOneOfExplicitArrayBranches": {
"$comment": "Case 7: each oneOf branch pins `type: array`, so the intersection with the outer type union must prune the non-array primitives. Only array variants should be emitted.",
"type": [ "string", "array" ],
"oneOf": [
{ "type": "array", "items": { "type": "string" } },
{ "type": "array", "items": { "type": "number" } }
]
},
"TypeArrayPartiallyUnsatisfiableOneOf": {
"$comment": "Some oneOf branches conflict with the outer type union and should be dropped during merge; the surviving branch must carry the outer type union. The two eliminated branches use object/number which the outer `[string, array]` disallows.",
"type": [ "string", "array" ],
"oneOf": [
{ "type": "object", "properties": { "name": { "type": "string" } } },
{ "items": { "type": "string" } },
{ "type": "number" }
]
},
"TypeArrayFullyUnsatisfiableOneOf": {
"$comment": "Case 9: every branch conflicts with the outer type union, so `try_merge_with_each_subschema` returns empty and the schema resolves to never. Must emit an empty enum cleanly rather than panic.",
"type": [ "string", "number" ],
"oneOf": [
{ "type": "array", "items": { "type": "string" } },
{ "type": "object", "properties": { "k": { "type": "string" } } }
]
},
"TypeArrayOneOfAndAllOf": {
"$comment": "Case 10: oneOf and allOf on the same object, both alongside a multi-type `type` array. Exercises the full merge path (allOf folded first, then oneOf fanned out) with the Vec instance_type flowing through the merge arm.",
"type": [ "string", "array" ],
"allOf": [
{ "minLength": 1 }
],
"oneOf": [
{ "items": { "type": "string" } },
{ "items": { "type": "number" } }
]
}
}
}
Loading