Skip to content

Commit aad817d

Browse files
committed
fix: generate Rust tuples from 2020-12 prefixItems + items:false
When normalizing prefixItems, inject minItems/maxItems matching the tuple length for closed tuples (items: false). This is required by typify's tuple detection which checks minItems == maxItems. Now `prefixItems: [number, number], items: false` correctly generates a Rust tuple type `(f64, f64)` instead of erroring. Added runtime test deserializing [42.5, -73.0] as Coordinates tuple.
1 parent a4a2617 commit aad817d

3 files changed

Lines changed: 56 additions & 1 deletion

File tree

typify-impl/src/normalize.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,28 @@ fn normalize_object(map: &mut Map<String, Value>) {
6060
}
6161

6262
// 2. prefixItems → items (as array), items → additionalItems
63+
// Also inject minItems/maxItems matching the tuple length so that
64+
// typify's tuple detection works (it requires both to be set).
6365
if let Some(prefix_items) = map.remove("prefixItems") {
66+
let tuple_len = prefix_items.as_array().map(|a| a.len());
67+
6468
// In 2020-12, `items` with `prefixItems` means "additionalItems"
6569
if let Some(items) = map.remove("items") {
70+
// items: false means no additional items beyond the tuple
71+
let is_closed = matches!(&items, Value::Bool(false));
6672
map.insert("additionalItems".to_string(), items);
73+
74+
// For closed tuples, set min/max to the tuple length
75+
if is_closed {
76+
if let Some(len) = tuple_len {
77+
map.entry("minItems".to_string())
78+
.or_insert_with(|| Value::Number(serde_json::Number::from(len)));
79+
map.entry("maxItems".to_string())
80+
.or_insert_with(|| Value::Number(serde_json::Number::from(len)));
81+
}
82+
}
6783
}
84+
6885
map.insert("items".to_string(), prefix_items);
6986
}
7087

@@ -259,6 +276,25 @@ mod tests {
259276
json!([{ "type": "string" }, { "type": "integer" }])
260277
);
261278
assert_eq!(schema["additionalItems"], json!(false));
279+
// Closed tuple should have minItems/maxItems set for typify tuple detection
280+
assert_eq!(schema["minItems"], json!(2));
281+
assert_eq!(schema["maxItems"], json!(2));
282+
}
283+
284+
#[test]
285+
fn test_prefix_items_open_no_min_max() {
286+
// When items is not false (open tuple), don't inject min/max
287+
let mut schema = json!({
288+
"$schema": "https://json-schema.org/draft/2020-12/schema",
289+
"type": "array",
290+
"prefixItems": [
291+
{ "type": "string" }
292+
],
293+
"items": { "type": "integer" }
294+
});
295+
normalize_schema(&mut schema);
296+
assert!(schema.get("minItems").is_none());
297+
assert!(schema.get("maxItems").is_none());
262298
}
263299

264300
#[test]

typify-test/build.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,22 @@ fn main() {
267267
"Tag": {
268268
"type": "string",
269269
"minLength": 1
270+
},
271+
"Coordinates": {
272+
"type": "array",
273+
"prefixItems": [
274+
{ "type": "number" },
275+
{ "type": "number" }
276+
],
277+
"items": false
270278
}
271279
},
272280
"type": "object",
273281
"title": "Location",
274282
"properties": {
275283
"address": { "$ref": "#/$defs/Address" },
276-
"tag": { "$ref": "#/$defs/Tag" }
284+
"tag": { "$ref": "#/$defs/Tag" },
285+
"coords": { "$ref": "#/$defs/Coordinates" }
277286
},
278287
"required": ["address"],
279288
"dependentRequired": {

typify-test/src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,16 @@ fn test_2020_12_defs_and_dependent_required() {
325325
assert_eq!(v.address.city, "Anytown");
326326
}
327327

328+
#[test]
329+
fn test_2020_12_prefix_items_tuple() {
330+
// prefixItems + items:false should generate a proper Rust tuple
331+
let json = r#"[42.5, -73.0]"#;
332+
let v: schema_2020_12::Coordinates = serde_json::from_str(json).unwrap();
333+
// Coordinates is a newtype around (f64, f64)
334+
assert_eq!((v.0).0, 42.5);
335+
assert_eq!((v.0).1, -73.0);
336+
}
337+
328338
// --- External $ref support ---
329339

330340
#[test]

0 commit comments

Comments
 (0)