From dfb41f5aea569e06aac0dfd09b2fe4ffe1fbbd3a Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:42:57 +0200 Subject: [PATCH] fix(cli): reject --pdf-options that is not a JSON object instead of panicking (#731) --- apps/designcraft-cli/src/main.rs | 6 +++++- apps/designcraft-cli/tests/cli.rs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/designcraft-cli/src/main.rs b/apps/designcraft-cli/src/main.rs index dd62cd91..4bee26a7 100644 --- a/apps/designcraft-cli/src/main.rs +++ b/apps/designcraft-cli/src/main.rs @@ -261,7 +261,11 @@ fn run(args: &[String]) -> Result<(), String> { "--page" => page = val()?.parse().map_err(|_| "bad --page")?, "--scale" => scale = val()?.parse().map_err(|_| "bad --scale")?, "--pdf-options" => { - pdf_opts = serde_json::from_str(&val()?).map_err(|e| format!("--pdf-options: {e}"))?; + pdf_opts = match serde_json::from_str(&val()?).map_err(|e| format!("--pdf-options: {e}"))? { + Value::Null => json!({}), + o @ Value::Object(_) => o, + _ => return Err("--pdf-options: expected a JSON object".to_string()), + }; } "--export" => { let out = val()?; diff --git a/apps/designcraft-cli/tests/cli.rs b/apps/designcraft-cli/tests/cli.rs index 92f68ab5..b2eed1b7 100644 --- a/apps/designcraft-cli/tests/cli.rs +++ b/apps/designcraft-cli/tests/cli.rs @@ -138,6 +138,24 @@ fn run_export_options_come_before_their_export() { let _ = std::fs::remove_dir_all(&dir); } +/// `--pdf-options` must be a JSON object; any other JSON value used to panic when the PDF path was set on it. +#[test] +fn pdf_options_that_are_not_an_object_are_an_error_not_a_panic() { + let dir = std::env::temp_dir().join(format!("dc-cli-pdfopts-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let pdf = dir.join("out.pdf"); + let pdf = pdf.to_str().unwrap(); + for val in ["[]", "true", "1", r#""x""#] { + let out = cli().args(["run", "--sample", "--pdf-options", val, "--export", pdf]).output().unwrap(); + let err = String::from_utf8_lossy(&out.stderr); + assert_eq!(out.status.code(), Some(1), "{val}: {err}"); + assert!(err.contains("--pdf-options") && !err.contains("panicked"), "{val}: {err}"); + } + let out = cli().args(["run", "--sample", "--pdf-options", "null", "--export", pdf]).output().unwrap(); + assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); + let _ = std::fs::remove_dir_all(&dir); +} + #[cfg(unix)] #[test] fn raster_export_preserves_an_existing_reader_and_private_symlink_target() {