diff --git a/src/cmds/cloud/curl_cmd.rs b/src/cmds/cloud/curl_cmd.rs index d6930ef6..438bb959 100644 --- a/src/cmds/cloud/curl_cmd.rs +++ b/src/cmds/cloud/curl_cmd.rs @@ -54,35 +54,30 @@ pub fn run(args: &[String], verbose: u8) -> Result { fn filter_curl_output(output: &str) -> String { let trimmed = output.trim(); - // Try JSON detection: starts with { or [ + // JSON output: pass through unchanged to preserve validity for piping (#1015) if (trimmed.starts_with('{') || trimmed.starts_with('[')) && (trimmed.ends_with('}') || trimmed.ends_with(']')) { - if let Ok(schema) = json_cmd::filter_json_string(trimmed, 5) { - // Only use schema if it's actually shorter than the original (#297) - if schema.len() <= trimmed.len() { - return schema; - } - } + return trimmed.to_string(); } - // Not JSON: truncate long output + // Non-JSON: truncate long output let lines: Vec<&str> = trimmed.lines().collect(); - if lines.len() > 30 { - let mut result: Vec<&str> = lines[..30].to_vec(); + if lines.len() > 50 { + let mut result: Vec<&str> = lines[..50].to_vec(); result.push(""); let msg = format!( "... ({} more lines, {} bytes total)", - lines.len() - 30, + lines.len() - 50, trimmed.len() ); return format!("{}\n{}", result.join("\n"), msg); } - // Short output: return as-is but truncate long lines + // Short non-JSON output: truncate long lines lines .iter() - .map(|l| truncate(l, 200)) + .map(|l| truncate(l, 300)) .collect::>() .join("\n") } @@ -92,20 +87,15 @@ mod tests { use super::*; #[test] - fn test_filter_curl_json() { - // Large JSON where schema is shorter than original — schema should be returned - let output = r#"{"name": "a very long user name here", "count": 42, "items": [1, 2, 3], "description": "a very long description that takes up many characters in the original JSON payload", "status": "active", "url": "https://example.com/api/v1/users/123"}"#; + fn test_filter_curl_json_preserves_valid_json() { + // curl output must remain valid JSON for downstream parsers (#1015) + let output = r#"{"name": "test", "count": 42, "items": [1, 2, 3]}"#; let result = filter_curl_output(output); - assert!(result.contains("name")); - assert!(result.contains("string")); - assert!(result.contains("int")); - } - - #[test] - fn test_filter_curl_json_array() { - let output = r#"[{"id": 1}, {"id": 2}]"#; - let result = filter_curl_output(output); - assert!(result.contains("id")); + assert!(result.contains("\"name\"")); + assert!(result.contains("42")); + // Must be parseable as JSON + assert!(serde_json::from_str::(&result).is_ok(), + "curl output must be valid JSON: {}", result); } #[test] @@ -118,21 +108,18 @@ mod tests { #[test] fn test_filter_curl_json_small_returns_original() { - // Small JSON where schema would be larger than original (issue #297) let output = r#"{"r2Ready":true,"status":"ok"}"#; let result = filter_curl_output(output); - // Schema would be "{\n r2Ready: bool,\n status: string\n}" which is longer - // Should return the original JSON unchanged assert_eq!(result.trim(), output.trim()); } #[test] fn test_filter_curl_long_output() { - let lines: Vec = (0..50).map(|i| format!("Line {}", i)).collect(); + let lines: Vec = (0..80).map(|i| format!("Line {}", i)).collect(); let output = lines.join("\n"); let result = filter_curl_output(&output); assert!(result.contains("Line 0")); - assert!(result.contains("Line 29")); + assert!(result.contains("Line 49")); assert!(result.contains("more lines")); } } diff --git a/src/cmds/system/json_cmd.rs b/src/cmds/system/json_cmd.rs index 4e887417..b6e700bc 100644 --- a/src/cmds/system/json_cmd.rs +++ b/src/cmds/system/json_cmd.rs @@ -250,12 +250,12 @@ fn extract_schema(value: &Value, depth: usize, max_depth: usize) -> String { if is_simple { if i < keys.len() - 1 { - lines.push(format!("{} {}: {},", indent, key, val_trimmed)); + lines.push(format!("{} \"{}\": {},", indent, key, val_trimmed)); } else { - lines.push(format!("{} {}: {}", indent, key, val_trimmed)); + lines.push(format!("{} \"{}\": {}", indent, key, val_trimmed)); } } else { - lines.push(format!("{} {}:", indent, key)); + lines.push(format!("{} \"{}\":", indent, key)); lines.push(val_schema); } @@ -316,16 +316,30 @@ mod tests { fn test_extract_schema_simple() { let json: Value = serde_json::from_str(r#"{"name": "test", "count": 42}"#).unwrap(); let schema = extract_schema(&json, 0, 5); - assert!(schema.contains("name")); + assert!(schema.contains("\"name\"")); assert!(schema.contains("string")); assert!(schema.contains("int")); } + #[test] + fn test_extract_schema_keys_are_quoted() { + let json: Value = + serde_json::from_str(r#"{"id": 1, "status": "open", "nested": {"key": "val"}}"#) + .unwrap(); + let schema = extract_schema(&json, 0, 5); + assert!(schema.contains("\"id\":"), "keys must be double-quoted: {}", schema); + assert!(schema.contains("\"status\":"), "keys must be double-quoted: {}", schema); + assert!(schema.contains("\"nested\":"), "keys must be double-quoted: {}", schema); + assert!(schema.contains("\"key\":"), "nested keys must be double-quoted: {}", schema); + // Verify the output is parseable as valid JSON (values replaced with types) + // At minimum, keys should be properly quoted + } + #[test] fn test_extract_schema_array() { let json: Value = serde_json::from_str(r#"{"items": [1, 2, 3]}"#).unwrap(); let schema = extract_schema(&json, 0, 5); - assert!(schema.contains("items")); + assert!(schema.contains("\"items\"")); assert!(schema.contains("(3)")); } }