From b576c72d0b83fd8296b7e7d1f384539c7078ca1f Mon Sep 17 00:00:00 2001 From: Dominic Anaeto Date: Mon, 26 Oct 2026 22:59:20 -0400 Subject: [PATCH] LC-002: reject invalid script parameters --- apps/lightcraft-cli/src/main.rs | 6 +++++- apps/lightcraft-cli/tests/it/cli.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/lightcraft-cli/src/main.rs b/apps/lightcraft-cli/src/main.rs index c1f9d258c..84ca49d21 100644 --- a/apps/lightcraft-cli/src/main.rs +++ b/apps/lightcraft-cli/src/main.rs @@ -565,7 +565,11 @@ fn run(args: &[String]) -> Result<(), String> { .or(v.get("method")) .and_then(Value::as_str) .ok_or_else(|| format!("{path}:{}: needs `command` or `method`", n + 1))?; - let params = v.get("params").cloned().filter(|p| p.is_object()).unwrap_or_else(|| json!({})); + let params = match v.get("params") { + None | Some(Value::Null) => json!({}), + Some(Value::Object(params)) => Value::Object(params.clone()), + Some(_) => return Err(format!("{path}:{}: `params` must be a JSON object", n + 1)), + }; steps.push(Step { id: id.to_string(), params }); } } diff --git a/apps/lightcraft-cli/tests/it/cli.rs b/apps/lightcraft-cli/tests/it/cli.rs index 8a11f5c9a..8622e7c18 100644 --- a/apps/lightcraft-cli/tests/it/cli.rs +++ b/apps/lightcraft-cli/tests/it/cli.rs @@ -513,6 +513,32 @@ fn run_cli(args: &[&str], stdin: Option<&str>) -> (bool, Vec, String) { (o.status.success(), lines, String::from_utf8_lossy(&o.stderr).to_string()) } +#[test] +fn run_script_params_validate_before_any_commands() { + let path = tmp("run-invalid-params.jsonl"); + let path_str = path.to_str().unwrap(); + for params in [json!([]), json!("invalid"), json!(42), json!(true)] { + let script = format!("{}\n{}\n", json!({"command": "library.info"}), json!({"command": "develop.reset", "params": params})); + std::fs::write(&path, &script).unwrap(); + for (source, stdin) in [("-", Some(script.as_str())), (path_str, None)] { + let (ok, lines, err) = run_cli(&["--script", source], stdin); + assert!(!ok, "{source}: {params}: {lines:?}"); + assert!(lines.is_empty(), "{source}: {params}: {lines:?}"); + assert!(err.contains(&format!("{source}:2")), "{err}"); + assert!(err.contains("`params` must be a JSON object"), "{err}"); + } + } + for script in + ["{\"command\":\"library.info\"}\n", "{\"command\":\"library.info\",\"params\":null}\n", "{\"command\":\"library.info\",\"params\":{}}\n"] + { + let (ok, lines, err) = run_cli(&["--script", "-"], Some(script)); + assert!(ok, "{script}: {err}"); + assert_eq!(lines.len(), 1, "{script}: {lines:?}"); + assert_eq!(lines[0]["ok"], true, "{script}: {lines:?}"); + } + std::fs::remove_file(path).unwrap(); +} + #[test] fn run_subcommand_chains_commands_and_persists_a_library() { let input = tmp("run-in.png");