diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index f0f1141f..a9d91aff 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -355,6 +355,11 @@ pub(crate) fn with_param(p: &Value, key: &str, v: Value) -> Value { /// Targets: `ids` / `id` params, else the selection. pub(crate) fn targets(s: &Session, p: &Value) -> Result> { + if let Some(a) = p.get("ids").and_then(Value::as_array) + && let Some(v) = a.iter().find(|v| v.as_u64().and_then(|v| u32::try_from(v).ok()).is_none()) + { + return Err(bad("shape", format!("`ids` has an invalid shape id: {v}"))); + } if let Some(ids) = ids_param(p, "ids") && !ids.is_empty() { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 18f55827..5105fcf3 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -5515,3 +5515,15 @@ fn transition_timing_invalid_after_keeps_value() { s.execute("transition.timing", &json!({"after": null})).unwrap(); assert_eq!(after(&s), None); } + + +#[test] +fn invalid_ids_array_never_falls_back_to_the_selection() { + let mut s = session(); + let a = s.execute("shape.insert", &json!({"preset": "rect", "rect": [100, 100, 100, 100]})).unwrap()["id"].as_u64().unwrap(); + for ids in [json!([-1]), json!([1.5]), json!([4294967296u64]), json!([-1, a])] { + let r = s.execute("edit.delete", &json!({ "ids": ids })); + assert!(matches!(r, Err(crate::EngineError::BadParams { .. })), "invalid ids {ids} gave {r:?}"); + assert!(s.doc().unwrap().shape(deckcraft_model::ShapeId(a as u32)).is_some(), "shape deleted for {ids}"); + } +}