diff --git a/crates/engine/src/cmd/extra.rs b/crates/engine/src/cmd/extra.rs index a08f080..21dbd90 100644 --- a/crates/engine/src/cmd/extra.rs +++ b/crates/engine/src/cmd/extra.rs @@ -1052,8 +1052,13 @@ fn toggle_checkbox(s: &mut Session, p: &Json) -> Result { }) } +/// Copy of the params that is always a JSON object, so keys can be assigned; any other JSON value counts as no params (like null). +fn object_params(p: &Json) -> Json { + Json::Object(p.as_object().cloned().unwrap_or_default()) +} + fn theme_colors(s: &mut Session, p: &Json) -> Result { - let mut params = p.clone(); + let mut params = object_params(p); if params.get("name").is_none() { params["name"] = json!(s.doc()?.wb.theme.name.clone()); } @@ -1074,7 +1079,7 @@ fn background(s: &mut Session, p: &Json) -> Result { Ok(Json::Null) }); } - let mut params = p.clone(); + let mut params = object_params(p); params["alt"] = json!("Sheet background"); params["at"] = json!("A1"); s.execute("insert.picture", params) @@ -1090,7 +1095,7 @@ fn draw_border(s: &mut Session, p: &Json) -> Result { "outline" => "outside", _ => "all", }; - let mut params = p.clone(); + let mut params = object_params(p); params["preset"] = json!(preset); s.execute("home.borders", params) } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 33fdc11..8aa6e95 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1696,6 +1696,23 @@ fn every_command_survives_empty_params() { } } +#[test] +fn non_object_params_do_not_panic_in_wrapper_commands() { + // Issue #516. + for cmd in ["pageLayout.themeColors", "pageLayout.background", "home.drawBorder"] { + for params in [json!([]), json!(5), json!("x"), json!(true)] { + let mut s = s(); + let r = s.execute(cmd, params.clone()); + assert!(!matches!(r, Err(crate::EngineError::Internal(..))), "{cmd} {params}: {r:?}"); + } + } + // A saved script step forwards its params unchanged into the same call. + let mut s = s(); + s.execute("automate.newScript", json!({"name": "bad", "commands": [{"command": "home.drawBorder", "params": []}]})).unwrap(); + let r = s.execute("automate.runScript", json!({"name": "bad"})); + assert!(!matches!(r, Err(crate::EngineError::Internal(..))), "script: {r:?}"); +} + fn fmt_of(s: &Session, a: &str) -> String { let wb = &s.doc().unwrap().wb; let sh = wb.active().unwrap();