diff --git a/crates/engine/src/cmd/shaper.rs b/crates/engine/src/cmd/shaper.rs index f9e5e52b..c85f9613 100644 --- a/crates/engine/src/cmd/shaper.rs +++ b/crates/engine/src/cmd/shaper.rs @@ -500,7 +500,10 @@ pub(crate) fn refresh(before: &Document, d: &mut Document) -> Result<()> { fn candidates(s: &Session, p: &Value, points: &[Point], tolerance: f64) -> Result> { let st = s.doc()?; let area = points.iter().fold(Rect::new(f64::MAX, f64::MAX, f64::MIN, f64::MIN), |b, p| b.union_pt(*p)).inflate(tolerance, tolerance); - let supplied = p.get("ids").and_then(Value::as_array).map(|v| v.iter().filter_map(Value::as_u64).map(NodeId).collect::>()); + let supplied = match p.get("ids") { + Some(_) => Some(checked_ids_param(s, p, "ids", "shaper.scribble")?), + None => None, + }; let expand = supplied.is_none() && st.selection.objects.len() < 2; let ids = supplied.unwrap_or_else(|| if st.selection.objects.len() > 1 { st.selection.objects.clone() } else { st.doc.selectable_art() }); let eligible: Vec = ids diff --git a/crates/engine/src/tests_shaper.rs b/crates/engine/src/tests_shaper.rs index a3275608..77e8da0a 100644 --- a/crates/engine/src/tests_shaper.rs +++ b/crates/engine/src/tests_shaper.rs @@ -42,6 +42,22 @@ fn stroke(s: &Session, id: NodeId, x: f64, y: f64) -> bool { }) } +/// An invalid explicit Shaper target must never silently change which shapes get edited. +#[test] +fn shaper_scribble_rejects_malformed_explicit_targets() { + let mut s = session(); + let a = rectangle(&mut s, 0.0, "#ff0000"); + let untouched = s.doc().unwrap().doc.layers.clone(); + let undo = s.doc().unwrap().history.undo.len(); + for ids in [json!([a.0, "bad"]), json!([a.0, 999999]), json!({"id": a.0}), json!("bad")] { + let err = s.execute("shaper.scribble", &json!({"points": [[25, 20], [35, 30]], "ids": ids})); + assert!(err.is_err(), "malformed targets were accepted"); + assert_eq!(s.doc().unwrap().doc.layers, untouched); + assert_eq!(s.doc().unwrap().history.undo.len(), undo); + } + s.execute("shaper.scribble", &json!({"points": [[25, 20], [35, 30]], "ids": [a.0]})).unwrap(); +} + #[test] fn shaper_merges_with_origin_color_keeps_live_originals_and_undoes_once() { for reverse in [false, true] {