From 15c9ea72691545750d9f3c819afc7c98c189417c Mon Sep 17 00:00:00 2001 From: Anshul Garg Date: Wed, 11 Mar 2026 21:43:24 +0530 Subject: [PATCH 1/3] fix(sheets): preserve multi-row structure in +append --json-values Previously, `parse_append_args` called `.flatten()` on the parsed `Vec>`, collapsing all rows into a single flat vector. Combined with `build_append_request` wrapping the flat vec in another array, this meant `[["Alice","100"],["Bob","200"]]` produced one row with four columns instead of two rows with two columns each. Change `AppendConfig.values` from `Vec` to `Vec>` so row boundaries are preserved end-to-end. Single-row inputs via `--values` and flat JSON arrays are wrapped in a single-element outer vec for consistency. Closes #311 --- .../fix-append-json-values-multi-row.md | 5 ++ src/helpers/sheets.rs | 76 +++++++++++++++---- 2 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 .changeset/fix-append-json-values-multi-row.md diff --git a/.changeset/fix-append-json-values-multi-row.md b/.changeset/fix-append-json-values-multi-row.md new file mode 100644 index 00000000..bc3979b5 --- /dev/null +++ b/.changeset/fix-append-json-values-multi-row.md @@ -0,0 +1,5 @@ +--- +"@googleworkspace/cli": patch +--- + +Fix `+append --json-values` flattening multi-row arrays into a single row by preserving the `Vec>` row structure through to the API request body diff --git a/src/helpers/sheets.rs b/src/helpers/sheets.rs index 5c2a9e90..7cece391 100644 --- a/src/helpers/sheets.rs +++ b/src/helpers/sheets.rs @@ -218,11 +218,8 @@ fn build_append_request( "valueInputOption": "USER_ENTERED" }); - // We use `json!` macro to construct a generic JSON Value for the request body. - // This allows us to easily create nested objects without defining explicit structs - // for every API request body. let body = json!({ - "values": [config.values] + "values": config.values }); // Map `&String` scope URLs to owned `String`s for the return value @@ -263,24 +260,26 @@ fn build_read_request( pub struct AppendConfig { /// The ID of the spreadsheet to append to. pub spreadsheet_id: String, - /// The values to append, as a vector of strings. - pub values: Vec, + /// The rows to append, where each inner Vec represents one row. + pub values: Vec>, } /// Parses arguments for the `+append` command. /// -/// Splits the comma-separated `values` argument into a `Vec`. +/// Supports both `--values` (single row) and `--json-values` (single or multi-row). pub fn parse_append_args(matches: &ArgMatches) -> AppendConfig { let values = if let Some(json_str) = matches.get_one::("json-values") { - // Parse JSON array of rows + // Try parsing as array-of-arrays (multi-row) first if let Ok(parsed) = serde_json::from_str::>>(json_str) { - parsed.into_iter().flatten().collect() + parsed + } else if let Ok(parsed) = serde_json::from_str::>(json_str) { + // Single flat array — treat as one row + vec![parsed] } else { - // Treat as single row JSON array - serde_json::from_str::>(json_str).unwrap_or_default() + Vec::new() } } else if let Some(values_str) = matches.get_one::("values") { - values_str.split(',').map(|s| s.to_string()).collect() + vec![values_str.split(',').map(|s| s.to_string()).collect()] } else { Vec::new() }; @@ -365,7 +364,7 @@ mod tests { let doc = make_mock_doc(); let config = AppendConfig { spreadsheet_id: "123".to_string(), - values: vec!["a".to_string(), "b".to_string(), "c".to_string()], + values: vec![vec!["a".to_string(), "b".to_string(), "c".to_string()]], }; let (params, body, scopes) = build_append_request(&config, &doc).unwrap(); @@ -391,11 +390,58 @@ mod tests { } #[test] - fn test_parse_append_args() { + fn test_parse_append_args_values() { let matches = make_matches_append(&["test", "--spreadsheet", "123", "--values", "a,b,c"]); let config = parse_append_args(&matches); assert_eq!(config.spreadsheet_id, "123"); - assert_eq!(config.values, vec!["a", "b", "c"]); + assert_eq!(config.values, vec![vec!["a", "b", "c"]]); + } + + #[test] + fn test_parse_append_args_json_single_row() { + let matches = make_matches_append(&[ + "test", + "--spreadsheet", + "123", + "--json-values", + r#"["a","b","c"]"#, + ]); + let config = parse_append_args(&matches); + assert_eq!(config.values, vec![vec!["a", "b", "c"]]); + } + + #[test] + fn test_parse_append_args_json_multi_row() { + let matches = make_matches_append(&[ + "test", + "--spreadsheet", + "123", + "--json-values", + r#"[["Alice","100"],["Bob","200"]]"#, + ]); + let config = parse_append_args(&matches); + assert_eq!( + config.values, + vec![vec!["Alice", "100"], vec!["Bob", "200"]] + ); + } + + #[test] + fn test_build_append_request_multi_row() { + let doc = make_mock_doc(); + let config = AppendConfig { + spreadsheet_id: "123".to_string(), + values: vec![ + vec!["Alice".to_string(), "100".to_string()], + vec!["Bob".to_string(), "200".to_string()], + ], + }; + let (_params, body, _scopes) = build_append_request(&config, &doc).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&body).unwrap(); + let values = parsed["values"].as_array().unwrap(); + assert_eq!(values.len(), 2); + assert_eq!(values[0], json!(["Alice", "100"])); + assert_eq!(values[1], json!(["Bob", "200"])); } #[test] From 7345d1b7e03fbba2b3982803be5396a5c064c783 Mon Sep 17 00:00:00 2001 From: Anshul Garg Date: Wed, 11 Mar 2026 22:22:01 +0530 Subject: [PATCH 2/3] chore: trigger CLA re-check From a881528b5d98eeb642d63b4208c73ec4221fc570 Mon Sep 17 00:00:00 2001 From: Anshul Garg Date: Wed, 11 Mar 2026 23:01:43 +0530 Subject: [PATCH 3/3] fix: warn on malformed --json-values input instead of silently ignoring Address review feedback: print a warning to stderr when --json-values cannot be parsed as a JSON array, rather than silently falling back to an empty value set. --- src/helpers/sheets.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers/sheets.rs b/src/helpers/sheets.rs index 7cece391..173deb67 100644 --- a/src/helpers/sheets.rs +++ b/src/helpers/sheets.rs @@ -276,6 +276,7 @@ pub fn parse_append_args(matches: &ArgMatches) -> AppendConfig { // Single flat array — treat as one row vec![parsed] } else { + eprintln!("Warning: --json-values is not valid JSON; expected an array or array-of-arrays"); Vec::new() } } else if let Some(values_str) = matches.get_one::("values") {