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 = format_output(&[json!(["a", "b", "c"])], false, true, &OutputFormat::Auto);
220 assert_eq!(output, "a\nb\nc");
221 }
222
223 #[test]
224 fn format_lines_multiple() {
225 let output = format_output(&[json!("x"), json!("y")], false, true, &OutputFormat::Auto);
226 assert_eq!(output, "x\ny");
227 }
228
229 #[test]
230 fn format_empty() {
231 assert_eq!(format_output(&[], false, false, &OutputFormat::Auto), "");
232 }
233
234 #[test]
235 fn format_empty_string() {
236 assert_eq!(
237 format_output(&[json!("")], false, false, &OutputFormat::Auto),
238 ""
239 );
240 }
241
242 #[test]
243 fn format_string_with_newlines() {
244 assert_eq!(
245 format_output(&[json!("line1\nline2")], false, false, &OutputFormat::Auto),
246 "line1\nline2"
247 );
248 }
249
250 #[test]
253 fn format_output_yaml() {
254 let output = format_output(
255 &[json!({"name": "Alice"})],
256 false,
257 false,
258 &OutputFormat::Yaml,
259 );
260 assert!(output.contains("name:"));
261 assert!(output.contains("Alice"));
262 }
263
264 #[test]
265 fn format_output_toml() {
266 let output = format_output(
267 &[json!({"name": "Alice"})],
268 false,
269 false,
270 &OutputFormat::Toml,
271 );
272 assert!(output.contains("name"));
273 assert!(output.contains("Alice"));
274 }
275
276 #[test]
277 fn format_output_json_explicit() {
278 let output = format_output(
279 &[json!({"name": "Alice"})],
280 false,
281 false,
282 &OutputFormat::Json,
283 );
284 assert!(output.contains("\"name\""));
285 assert!(output.contains("\"Alice\""));
286 }
287
288 #[test]
289 fn format_yaml_scalar() {
290 let output = format_output(&[json!("hello")], false, false, &OutputFormat::Yaml);
291 assert!(output.contains("hello"));
292 }
293
294 #[test]
295 fn format_yaml_array() {
296 let output = format_output(&[json!([1, 2, 3])], false, false, &OutputFormat::Yaml);
297 assert!(output.contains("- 1"));
298 }
299
300 #[test]
301 fn format_toml_non_table_fallback() {
302 let output = format_output(&[json!("hello")], false, false, &OutputFormat::Toml);
304 assert_eq!(output, "hello");
305 }
306
307 #[test]
308 fn format_toml_nested() {
309 let output = format_output(
310 &[json!({"server": {"port": 8080}})],
311 false,
312 false,
313 &OutputFormat::Toml,
314 );
315 assert!(output.contains("[server]"));
316 assert!(output.contains("port = 8080"));
317 }
318
319 #[test]
326 fn format_multiple_as_yaml() {
327 let output = format_output(
328 &[json!({"a": 1}), json!({"b": 2})],
329 false,
330 false,
331 &OutputFormat::Yaml,
332 );
333 assert!(output.contains("a:"));
334 assert!(output.contains("b:"));
335 }
336
337 #[test]
338 fn format_multiple_as_toml() {
339 let output = format_output(
340 &[json!({"a": 1}), json!({"b": 2})],
341 false,
342 false,
343 &OutputFormat::Toml,
344 );
345 assert!(output.contains("results"));
347 }
348
349 #[test]
350 fn format_multiple_as_json() {
351 let output = format_output(
352 &[json!("a"), json!("b"), json!("c")],
353 false,
354 false,
355 &OutputFormat::Json,
356 );
357 assert!(output.contains('['));
358 assert!(output.contains("\"a\""));
359 assert!(output.contains("\"b\""));
360 assert!(output.contains("\"c\""));
361 }
362
363 #[test]
366 fn format_yaml_nested_object() {
367 let output = format_output(
368 &[json!({"server": {"host": "localhost", "port": 8080}})],
369 false,
370 false,
371 &OutputFormat::Yaml,
372 );
373 assert!(output.contains("server:"));
374 assert!(output.contains("host:"));
375 assert!(output.contains("localhost"));
376 }
377
378 #[test]
379 fn format_yaml_array_of_objects() {
380 let output = format_output(
381 &[json!([{"name": "Alice"}, {"name": "Bob"}])],
382 false,
383 false,
384 &OutputFormat::Yaml,
385 );
386 assert!(output.contains("name: Alice"));
387 assert!(output.contains("name: Bob"));
388 }
389
390 #[test]
391 fn format_yaml_null() {
392 let output = format_output(&[json!(null)], false, false, &OutputFormat::Yaml);
393 assert!(output.contains("null"));
394 }
395
396 #[test]
397 fn format_yaml_boolean() {
398 let output = format_output(&[json!(true)], false, false, &OutputFormat::Yaml);
399 assert!(output.contains("true"));
400 }
401
402 #[test]
403 fn format_yaml_number() {
404 let output = format_output(&[json!(42)], false, false, &OutputFormat::Yaml);
405 assert!(output.contains("42"));
406 }
407
408 #[test]
411 fn format_toml_array_fallback() {
412 let output = format_output(&[json!([1, 2, 3])], false, false, &OutputFormat::Toml);
414 assert!(output.contains("1"));
416 }
417
418 #[test]
419 fn format_toml_boolean() {
420 let output = format_output(&[json!({"flag": true})], false, false, &OutputFormat::Toml);
421 assert!(output.contains("flag = true"));
422 }
423
424 #[test]
425 fn format_toml_string_with_quotes() {
426 let output = format_output(
427 &[json!({"name": "Alice"})],
428 false,
429 false,
430 &OutputFormat::Toml,
431 );
432 assert!(output.contains("name = \"Alice\""));
433 }
434
435 #[test]
436 fn format_toml_integer() {
437 let output = format_output(&[json!({"count": 42})], false, false, &OutputFormat::Toml);
438 assert!(output.contains("count = 42"));
439 }
440
441 #[test]
444 fn format_json_null() {
445 let output = format_output(&[json!(null)], true, false, &OutputFormat::Auto);
446 assert_eq!(output, "null");
447 }
448
449 #[test]
450 fn format_json_bool() {
451 let output = format_output(&[json!(true)], true, false, &OutputFormat::Auto);
452 assert_eq!(output, "true");
453 }
454
455 #[test]
456 fn format_json_object() {
457 let output = format_output(&[json!({"a": 1})], true, false, &OutputFormat::Auto);
458 assert!(output.contains("\"a\": 1"));
459 }
460
461 #[test]
462 fn format_json_array() {
463 let output = format_output(&[json!([1, 2])], true, false, &OutputFormat::Auto);
464 assert!(output.contains("1"));
465 assert!(output.contains("2"));
466 }
467
468 #[test]
471 fn format_lines_nested_arrays() {
472 let output = format_output(
473 &[json!(["a", "b"]), json!(["c", "d"])],
474 false,
475 true,
476 &OutputFormat::Auto,
477 );
478 assert_eq!(output, "a\nb\nc\nd");
479 }
480
481 #[test]
482 fn format_lines_mixed_types() {
483 let output = format_output(
484 &[json!("str"), json!(42), json!(true), json!(null)],
485 false,
486 true,
487 &OutputFormat::Auto,
488 );
489 assert_eq!(output, "str\n42\ntrue\nnull");
490 }
491
492 #[test]
493 fn format_lines_single_value() {
494 let output = format_output(&[json!("hello")], false, true, &OutputFormat::Auto);
495 assert_eq!(output, "hello");
496 }
497
498 #[test]
499 fn format_lines_empty() {
500 let output = format_output(&[], false, true, &OutputFormat::Auto);
501 assert_eq!(output, "");
502 }
503
504 #[test]
507 fn format_json_flag_overrides_yaml() {
508 let output = format_output(
510 &[json!({"name": "Alice"})],
511 true,
512 false,
513 &OutputFormat::Yaml,
514 );
515 assert!(output.contains("\"name\""));
516 assert!(output.contains("\"Alice\""));
517 }
518
519 #[test]
520 fn format_json_flag_overrides_toml() {
521 let output = format_output(
522 &[json!({"name": "Alice"})],
523 true,
524 false,
525 &OutputFormat::Toml,
526 );
527 assert!(output.contains("\"name\""));
528 }
529
530 #[test]
533 fn format_negative_number() {
534 assert_eq!(
535 format_output(&[json!(-5)], false, false, &OutputFormat::Auto),
536 "-5"
537 );
538 }
539
540 #[test]
541 fn format_large_number() {
542 assert_eq!(
543 format_output(&[json!(999999999)], false, false, &OutputFormat::Auto),
544 "999999999"
545 );
546 }
547
548 #[test]
549 fn format_deeply_nested_object() {
550 let val = json!({"a": {"b": {"c": 1}}});
551 let output = format_output(&[val], false, false, &OutputFormat::Auto);
552 assert!(output.contains("\"a\""));
553 assert!(output.contains("\"b\""));
554 assert!(output.contains("\"c\""));
555 }
556
557 #[test]
558 fn format_unicode_string() {
559 assert_eq!(
560 format_output(&[json!("hello 🌍")], false, false, &OutputFormat::Auto),
561 "hello 🌍"
562 );
563 }
564}