1use crate::cli::OutputFormat;
2use serde_json::Value;
3
4pub fn format_output(
5 results: &[Value],
6 as_json: bool,
7 as_lines: bool,
8 output_format: &OutputFormat,
9) -> String {
10 if results.is_empty() {
11 return String::new();
12 }
13
14 if as_json {
16 return format_as_json(results);
17 }
18
19 match output_format {
21 OutputFormat::Json => return format_as_json(results),
22 OutputFormat::Yaml => return format_as_yaml(results),
23 OutputFormat::Toml => return format_as_toml(results),
24 OutputFormat::Auto => {} }
26
27 if as_lines {
28 let mut all_values = Vec::new();
30 for r in results {
31 if let Value::Array(arr) = r {
32 all_values.extend(arr.iter().cloned());
33 } else {
34 all_values.push(r.clone());
35 }
36 }
37 return all_values
38 .iter()
39 .map(format_value_plain)
40 .collect::<Vec<_>>()
41 .join("\n");
42 }
43
44 if results.len() == 1 {
45 return format_value_plain(&results[0]);
46 }
47
48 results
50 .iter()
51 .map(format_value_plain)
52 .collect::<Vec<_>>()
53 .join("\n")
54}
55
56fn format_value_plain(value: &Value) -> String {
57 match value {
58 Value::Null => "null".to_string(),
59 Value::Bool(b) => b.to_string(),
60 Value::Number(n) => n.to_string(),
61 Value::String(s) => s.clone(),
62 Value::Array(_) | Value::Object(_) => serde_json::to_string_pretty(value).unwrap(),
64 }
65}
66
67fn format_as_json(results: &[Value]) -> String {
68 if results.len() == 1 {
69 return serde_json::to_string_pretty(&results[0]).unwrap();
70 }
71 let arr = Value::Array(results.to_vec());
72 serde_json::to_string_pretty(&arr).unwrap()
73}
74
75fn format_as_yaml(results: &[Value]) -> String {
76 if results.len() == 1 {
77 let mut s = serde_yaml::to_string(&results[0]).unwrap_or_default();
78 if s.ends_with('\n') {
80 s.pop();
81 }
82 if let Some(stripped) = s.strip_prefix("---\n") {
84 return stripped.to_string();
85 }
86 return s;
87 }
88 let arr = Value::Array(results.to_vec());
89 let mut s = serde_yaml::to_string(&arr).unwrap_or_default();
90 if s.ends_with('\n') {
91 s.pop();
92 }
93 if let Some(stripped) = s.strip_prefix("---\n") {
94 return stripped.to_string();
95 }
96 s
97}
98
99fn format_as_toml(results: &[Value]) -> String {
100 if results.len() == 1 {
101 return toml_from_json(&results[0]);
102 }
103 let wrapper = serde_json::json!({"results": results});
105 toml_from_json(&wrapper)
106}
107
108fn toml_from_json(value: &Value) -> String {
111 match value {
112 Value::Object(_) => {
113 let toml_val: Result<toml::Value, _> = serde_json::from_value(value.clone());
115 match toml_val {
116 Ok(tv) => {
117 let mut s = toml::to_string_pretty(&tv).unwrap_or_default();
118 if s.ends_with('\n') {
119 s.pop();
120 }
121 s
122 }
123 Err(_) => serde_json::to_string_pretty(value).unwrap(),
124 }
125 }
126 _ => format_value_plain(value),
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use serde_json::json;
135
136 #[test]
137 fn format_single_string() {
138 assert_eq!(
139 format_output(&[json!("hello")], false, false, &OutputFormat::Auto),
140 "hello"
141 );
142 }
143
144 #[test]
145 fn format_single_number() {
146 assert_eq!(
147 format_output(&[json!(42)], false, false, &OutputFormat::Auto),
148 "42"
149 );
150 }
151
152 #[test]
153 fn format_single_bool() {
154 assert_eq!(
155 format_output(&[json!(true)], false, false, &OutputFormat::Auto),
156 "true"
157 );
158 }
159
160 #[test]
161 fn format_single_null() {
162 assert_eq!(
163 format_output(&[json!(null)], false, false, &OutputFormat::Auto),
164 "null"
165 );
166 }
167
168 #[test]
169 fn format_single_float() {
170 let output = format_output(&[json!(3.14)], false, false, &OutputFormat::Auto);
171 assert!(output.starts_with("3.14"));
172 }
173
174 #[test]
175 fn format_object_plain() {
176 let output = format_output(&[json!({"a": 1})], false, false, &OutputFormat::Auto);
177 assert!(output.contains("\"a\""));
178 assert!(output.contains("1"));
179 }
180
181 #[test]
182 fn format_array_plain() {
183 let output = format_output(&[json!([1, 2, 3])], false, false, &OutputFormat::Auto);
184 assert!(output.contains("1"));
185 }
186
187 #[test]
188 fn format_multiple_results() {
189 let output = format_output(
190 &[json!("a"), json!("b"), json!("c")],
191 false,
192 false,
193 &OutputFormat::Auto,
194 );
195 assert_eq!(output, "a\nb\nc");
196 }
197
198 #[test]
199 fn format_json_single() {
200 let output = format_output(&[json!("hello")], true, false, &OutputFormat::Auto);
201 assert_eq!(output, "\"hello\"");
202 }
203
204 #[test]
205 fn format_json_number() {
206 let output = format_output(&[json!(42)], true, false, &OutputFormat::Auto);
207 assert_eq!(output, "42");
208 }
209
210 #[test]
211 fn format_json_multiple() {
212 let output = format_output(&[json!("a"), json!("b")], true, false, &OutputFormat::Auto);
213 assert!(output.contains('['));
214 assert!(output.contains("\"a\""));
215 }
216
217 #[test]
218 fn format_lines_array() {
219 let output =
220 format_output(&[json!(["a", "b", "c"])], false, true, &OutputFormat::Auto);
221 assert_eq!(output, "a\nb\nc");
222 }
223
224 #[test]
225 fn format_lines_multiple() {
226 let output =
227 format_output(&[json!("x"), json!("y")], false, true, &OutputFormat::Auto);
228 assert_eq!(output, "x\ny");
229 }
230
231 #[test]
232 fn format_empty() {
233 assert_eq!(
234 format_output(&[], false, false, &OutputFormat::Auto),
235 ""
236 );
237 }
238
239 #[test]
240 fn format_empty_string() {
241 assert_eq!(
242 format_output(&[json!("")], false, false, &OutputFormat::Auto),
243 ""
244 );
245 }
246
247 #[test]
248 fn format_string_with_newlines() {
249 assert_eq!(
250 format_output(&[json!("line1\nline2")], false, false, &OutputFormat::Auto),
251 "line1\nline2"
252 );
253 }
254
255 #[test]
258 fn format_output_yaml() {
259 let output =
260 format_output(&[json!({"name": "Alice"})], false, false, &OutputFormat::Yaml);
261 assert!(output.contains("name:"));
262 assert!(output.contains("Alice"));
263 }
264
265 #[test]
266 fn format_output_toml() {
267 let output =
268 format_output(&[json!({"name": "Alice"})], false, false, &OutputFormat::Toml);
269 assert!(output.contains("name"));
270 assert!(output.contains("Alice"));
271 }
272
273 #[test]
274 fn format_output_json_explicit() {
275 let output =
276 format_output(&[json!({"name": "Alice"})], false, false, &OutputFormat::Json);
277 assert!(output.contains("\"name\""));
278 assert!(output.contains("\"Alice\""));
279 }
280
281 #[test]
282 fn format_yaml_scalar() {
283 let output = format_output(&[json!("hello")], false, false, &OutputFormat::Yaml);
284 assert!(output.contains("hello"));
285 }
286
287 #[test]
288 fn format_yaml_array() {
289 let output =
290 format_output(&[json!([1, 2, 3])], false, false, &OutputFormat::Yaml);
291 assert!(output.contains("- 1"));
292 }
293
294 #[test]
295 fn format_toml_non_table_fallback() {
296 let output = format_output(&[json!("hello")], false, false, &OutputFormat::Toml);
298 assert_eq!(output, "hello");
299 }
300
301 #[test]
302 fn format_toml_nested() {
303 let output = format_output(
304 &[json!({"server": {"port": 8080}})],
305 false,
306 false,
307 &OutputFormat::Toml,
308 );
309 assert!(output.contains("[server]"));
310 assert!(output.contains("port = 8080"));
311 }
312
313 #[test]
320 fn format_multiple_as_yaml() {
321 let output = format_output(
322 &[json!({"a": 1}), json!({"b": 2})],
323 false,
324 false,
325 &OutputFormat::Yaml,
326 );
327 assert!(output.contains("a:"));
328 assert!(output.contains("b:"));
329 }
330
331 #[test]
332 fn format_multiple_as_toml() {
333 let output = format_output(
334 &[json!({"a": 1}), json!({"b": 2})],
335 false,
336 false,
337 &OutputFormat::Toml,
338 );
339 assert!(output.contains("results"));
341 }
342
343 #[test]
344 fn format_multiple_as_json() {
345 let output = format_output(
346 &[json!("a"), json!("b"), json!("c")],
347 false,
348 false,
349 &OutputFormat::Json,
350 );
351 assert!(output.contains('['));
352 assert!(output.contains("\"a\""));
353 assert!(output.contains("\"b\""));
354 assert!(output.contains("\"c\""));
355 }
356
357 #[test]
360 fn format_yaml_nested_object() {
361 let output = format_output(
362 &[json!({"server": {"host": "localhost", "port": 8080}})],
363 false,
364 false,
365 &OutputFormat::Yaml,
366 );
367 assert!(output.contains("server:"));
368 assert!(output.contains("host:"));
369 assert!(output.contains("localhost"));
370 }
371
372 #[test]
373 fn format_yaml_array_of_objects() {
374 let output = format_output(
375 &[json!([{"name": "Alice"}, {"name": "Bob"}])],
376 false,
377 false,
378 &OutputFormat::Yaml,
379 );
380 assert!(output.contains("name: Alice"));
381 assert!(output.contains("name: Bob"));
382 }
383
384 #[test]
385 fn format_yaml_null() {
386 let output = format_output(&[json!(null)], false, false, &OutputFormat::Yaml);
387 assert!(output.contains("null"));
388 }
389
390 #[test]
391 fn format_yaml_boolean() {
392 let output = format_output(&[json!(true)], false, false, &OutputFormat::Yaml);
393 assert!(output.contains("true"));
394 }
395
396 #[test]
397 fn format_yaml_number() {
398 let output = format_output(&[json!(42)], false, false, &OutputFormat::Yaml);
399 assert!(output.contains("42"));
400 }
401
402 #[test]
405 fn format_toml_array_fallback() {
406 let output = format_output(
408 &[json!([1, 2, 3])],
409 false,
410 false,
411 &OutputFormat::Toml,
412 );
413 assert!(output.contains("1"));
415 }
416
417 #[test]
418 fn format_toml_boolean() {
419 let output = format_output(
420 &[json!({"flag": true})],
421 false,
422 false,
423 &OutputFormat::Toml,
424 );
425 assert!(output.contains("flag = true"));
426 }
427
428 #[test]
429 fn format_toml_string_with_quotes() {
430 let output = format_output(
431 &[json!({"name": "Alice"})],
432 false,
433 false,
434 &OutputFormat::Toml,
435 );
436 assert!(output.contains("name = \"Alice\""));
437 }
438
439 #[test]
440 fn format_toml_integer() {
441 let output = format_output(
442 &[json!({"count": 42})],
443 false,
444 false,
445 &OutputFormat::Toml,
446 );
447 assert!(output.contains("count = 42"));
448 }
449
450 #[test]
453 fn format_json_null() {
454 let output = format_output(&[json!(null)], true, false, &OutputFormat::Auto);
455 assert_eq!(output, "null");
456 }
457
458 #[test]
459 fn format_json_bool() {
460 let output = format_output(&[json!(true)], true, false, &OutputFormat::Auto);
461 assert_eq!(output, "true");
462 }
463
464 #[test]
465 fn format_json_object() {
466 let output = format_output(&[json!({"a": 1})], true, false, &OutputFormat::Auto);
467 assert!(output.contains("\"a\": 1"));
468 }
469
470 #[test]
471 fn format_json_array() {
472 let output = format_output(&[json!([1, 2])], true, false, &OutputFormat::Auto);
473 assert!(output.contains("1"));
474 assert!(output.contains("2"));
475 }
476
477 #[test]
480 fn format_lines_nested_arrays() {
481 let output = format_output(
482 &[json!(["a", "b"]), json!(["c", "d"])],
483 false,
484 true,
485 &OutputFormat::Auto,
486 );
487 assert_eq!(output, "a\nb\nc\nd");
488 }
489
490 #[test]
491 fn format_lines_mixed_types() {
492 let output = format_output(
493 &[json!("str"), json!(42), json!(true), json!(null)],
494 false,
495 true,
496 &OutputFormat::Auto,
497 );
498 assert_eq!(output, "str\n42\ntrue\nnull");
499 }
500
501 #[test]
502 fn format_lines_single_value() {
503 let output = format_output(&[json!("hello")], false, true, &OutputFormat::Auto);
504 assert_eq!(output, "hello");
505 }
506
507 #[test]
508 fn format_lines_empty() {
509 let output = format_output(&[], false, true, &OutputFormat::Auto);
510 assert_eq!(output, "");
511 }
512
513 #[test]
516 fn format_json_flag_overrides_yaml() {
517 let output = format_output(
519 &[json!({"name": "Alice"})],
520 true,
521 false,
522 &OutputFormat::Yaml,
523 );
524 assert!(output.contains("\"name\""));
525 assert!(output.contains("\"Alice\""));
526 }
527
528 #[test]
529 fn format_json_flag_overrides_toml() {
530 let output = format_output(
531 &[json!({"name": "Alice"})],
532 true,
533 false,
534 &OutputFormat::Toml,
535 );
536 assert!(output.contains("\"name\""));
537 }
538
539 #[test]
542 fn format_negative_number() {
543 assert_eq!(
544 format_output(&[json!(-5)], false, false, &OutputFormat::Auto),
545 "-5"
546 );
547 }
548
549 #[test]
550 fn format_large_number() {
551 assert_eq!(
552 format_output(&[json!(999999999)], false, false, &OutputFormat::Auto),
553 "999999999"
554 );
555 }
556
557 #[test]
558 fn format_deeply_nested_object() {
559 let val = json!({"a": {"b": {"c": 1}}});
560 let output = format_output(&[val], false, false, &OutputFormat::Auto);
561 assert!(output.contains("\"a\""));
562 assert!(output.contains("\"b\""));
563 assert!(output.contains("\"c\""));
564 }
565
566 #[test]
567 fn format_unicode_string() {
568 assert_eq!(
569 format_output(&[json!("hello 🌍")], false, false, &OutputFormat::Auto),
570 "hello 🌍"
571 );
572 }
573}