1use indexmap::IndexSet;
2use serde_json::Value;
3use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
4
5use crate::{value_to_string, Error, OutputFormat};
6
7#[derive(Debug)]
8enum DataType {
9 SimpleList, ObjectArray, NestedArray, Mixed, }
14
15struct ColorScheme {
17 header: ColorSpec,
18 number: ColorSpec,
19 string: ColorSpec,
20 boolean: ColorSpec,
21 null: ColorSpec,
22 array_info: ColorSpec,
23}
24
25impl ColorScheme {
26 fn new() -> Self {
27 let mut header = ColorSpec::new();
28 header.set_fg(Some(Color::Blue)).set_bold(true);
29
30 let mut number = ColorSpec::new();
31 number.set_fg(Some(Color::Green));
32
33 let mut boolean = ColorSpec::new();
34 boolean.set_fg(Some(Color::Yellow));
35
36 let mut null = ColorSpec::new();
37 null.set_fg(Some(Color::Black)).set_intense(true); let mut array_info = ColorSpec::new();
40 array_info.set_fg(Some(Color::Cyan));
41
42 Self {
43 header,
44 number,
45 string: ColorSpec::new(), boolean,
47 null,
48 array_info,
49 }
50 }
51}
52
53fn should_use_colors() -> bool {
55 std::io::IsTerminal::is_terminal(&std::io::stdout()) && std::env::var("NO_COLOR").is_err()
56}
57
58fn get_color_for_value<'a>(value: &Value, colors: &'a ColorScheme) -> &'a ColorSpec {
60 match value {
61 Value::Number(_) => &colors.number,
62 Value::Bool(_) => &colors.boolean,
63 Value::Null => &colors.null,
64 _ => &colors.string,
65 }
66}
67
68pub fn format_output(data: &[Value], format: OutputFormat) -> Result<(), Error> {
69 if data.is_empty() {
70 return Ok(());
71 }
72
73 let use_colors = should_use_colors();
74
75 match format {
76 OutputFormat::Json => {
77 print_as_json(data, use_colors)?;
79 }
80 OutputFormat::Table => {
81 if is_object_array(data) {
83 print_as_table(data, use_colors)?;
84 } else {
85 let flattened = flatten_nested_arrays(data);
86 if is_object_array(&flattened) {
87 print_as_table(&flattened, use_colors)?;
88 } else {
89 return Err(Error::InvalidQuery(
90 "Cannot display as table: data is not object array".into(),
91 ));
92 }
93 }
94 }
95 OutputFormat::List => {
96 print_as_list(data, use_colors)?;
98 }
99 OutputFormat::Csv => {
100 print_as_csv(data, use_colors)?;
101 }
102 OutputFormat::Auto => {
103 match analyze_data_structure(data) {
105 DataType::SimpleList => print_as_list(data, use_colors)?,
106 DataType::ObjectArray => print_as_table(data, use_colors)?,
107 DataType::NestedArray => {
108 let flattened = flatten_nested_arrays(data);
109 if is_object_array(&flattened) {
110 print_as_table(&flattened, use_colors)?;
111 } else if is_simple_values(&flattened) {
112 print_as_list(&flattened, use_colors)?;
113 } else {
114 print_as_json(data, use_colors)?;
115 }
116 }
117 DataType::Mixed => print_as_json(data, use_colors)?,
118 }
119 }
120 }
121
122 Ok(())
123}
124
125fn analyze_data_structure(data: &[Value]) -> DataType {
126 if is_simple_values(data) {
127 return DataType::SimpleList;
128 }
129
130 if is_object_array(data) {
131 return DataType::ObjectArray;
132 }
133
134 if data.len() == 1 && data[0].is_array() {
136 return DataType::NestedArray;
137 }
138
139 DataType::Mixed
140}
141
142fn flatten_nested_arrays(data: &[Value]) -> Vec<Value> {
143 let mut flattened = Vec::new();
144
145 for item in data {
146 match item {
147 Value::Array(arr) => {
148 flattened.extend(arr.iter().cloned());
150 }
151 _ => {
152 flattened.push(item.clone());
153 }
154 }
155 }
156
157 flattened
158}
159
160fn collect_flattened_fields_ordered(value: &Value, prefix: &str, fields: &mut IndexSet<String>) {
161 match value {
162 Value::Object(obj) => {
163 for (key, val) in obj {
164 let field_name = if prefix.is_empty() {
166 key.clone()
167 } else {
168 format!("{}.{}", prefix, key)
169 };
170
171 match val {
172 Value::Object(_) => {
173 collect_flattened_fields_ordered(val, &field_name, fields);
174 }
175 _ => {
176 fields.insert(field_name);
177 }
178 }
179 }
180 }
181 _ => {
182 if !prefix.is_empty() {
183 fields.insert(prefix.to_string());
184 }
185 }
186 }
187}
188
189fn get_flattened_value(item: &Value, field_path: &str) -> String {
190 let parts: Vec<&str> = field_path.split('.').collect();
191 let mut current = item;
192
193 for part in parts {
194 match current.get(part) {
195 Some(val) => current = val,
196 None => return "".to_string(),
197 }
198 }
199
200 match current {
201 Value::Array(arr) => {
202 format!("[{} items]", arr.len())
204 }
205 _ => value_to_string(current),
206 }
207}
208
209fn get_field_value_for_coloring(item: &Value, field_path: &str) -> Value {
210 let parts: Vec<&str> = field_path.split('.').collect();
211 let mut current = item;
212
213 for part in parts {
214 match current.get(part) {
215 Some(val) => current = val,
216 None => return Value::Null,
217 }
218 }
219
220 current.clone()
221}
222
223fn get_value_type_info(value: &Value) -> &'static str {
224 match value {
225 Value::String(_) => "String",
226 Value::Number(_) => "Number",
227 Value::Bool(_) => "Boolean",
228 Value::Array(_) => "Array",
229 Value::Object(_) => "Object",
230 Value::Null => "Null",
231 }
232}
233
234fn get_sample_value(value: &Value) -> String {
235 match value {
236 Value::String(s) => format!("\"{}\"", s.chars().take(20).collect::<String>()),
237 Value::Number(n) => n.to_string(),
238 Value::Bool(b) => b.to_string(),
239 Value::Array(arr) => format!("[{} items]", arr.len()),
240 Value::Object(obj) => format!("{{{}...}}", obj.keys().next().unwrap_or(&"".to_string())),
241 Value::Null => "null".to_string(),
242 }
243}
244
245fn is_simple_values(data: &[Value]) -> bool {
246 data.iter()
247 .all(|v| matches!(v, Value::String(_) | Value::Number(_) | Value::Bool(_)))
248}
249
250fn is_object_array(data: &[Value]) -> bool {
251 data.iter().all(|v| v.is_object())
252}
253
254fn print_as_list(data: &[Value], use_colors: bool) -> Result<(), Error> {
255 if use_colors {
256 let mut stdout = StandardStream::stdout(ColorChoice::Always);
257 let colors = ColorScheme::new();
258
259 for item in data {
260 let color = get_color_for_value(item, &colors);
261 stdout.set_color(color)?;
262 print!("{}", value_to_string(item));
263 stdout.reset()?;
264 println!();
265 }
266 } else {
267 data.iter().for_each(|item| {
268 println!("{}", value_to_string(item));
269 });
270 }
271
272 Ok(())
273}
274
275fn print_as_json(data: &[Value], use_colors: bool) -> Result<(), Error> {
276 if use_colors {
277 print_colored_json_simple(data)?;
278 } else {
279 let json = serde_json::to_string_pretty(data).map_err(Error::Json)?;
280 println!("{}", json);
281 }
282
283 Ok(())
284}
285
286fn print_colored_json_simple(data: &[Value]) -> Result<(), Error> {
287 let mut stdout = StandardStream::stdout(ColorChoice::Always);
288 let colors = ColorScheme::new();
289
290 let json = serde_json::to_string_pretty(data).map_err(Error::Json)?;
292
293 for line in json.lines() {
294 let trimmed = line.trim();
295
296 if trimmed.starts_with('"') && trimmed.contains(':') {
298 if let Some(colon_pos) = trimmed.find(':') {
300 let key_part = &trimmed[..colon_pos + 1];
301 let value_part = &trimmed[colon_pos + 1..].trim();
302
303 let indent = &line[..line.len() - line.trim_start().len()];
305 print!("{}", indent);
306
307 stdout.set_color(&colors.header)?;
309 print!("{}", key_part);
310 stdout.reset()?;
311
312 print!(" ");
313
314 print_colored_json_value(value_part, &colors, &mut stdout)?;
316 println!();
317 } else {
318 println!("{}", line);
319 }
320 } else if trimmed.starts_with('{')
321 || trimmed.starts_with('}')
322 || trimmed.starts_with('[')
323 || trimmed.starts_with(']')
324 {
325 let indent = &line[..line.len() - line.trim_start().len()];
327 print!("{}", indent);
328 stdout.set_color(&colors.header)?;
329 print!("{}", trimmed);
330 stdout.reset()?;
331 println!();
332 } else {
333 let indent = &line[..line.len() - line.trim_start().len()];
335 print!("{}", indent);
336 print_colored_json_value(trimmed, &colors, &mut stdout)?;
337 println!();
338 }
339 }
340
341 Ok(())
342}
343
344fn print_colored_json_value(
345 value_str: &str,
346 colors: &ColorScheme,
347 stdout: &mut StandardStream,
348) -> Result<(), Error> {
349 let clean_value = value_str.trim_end_matches(',');
350
351 if clean_value == "null" {
352 stdout.set_color(&colors.null)?;
353 print!("{}", value_str);
354 stdout.reset()?;
355 } else if clean_value == "true" || clean_value == "false" {
356 stdout.set_color(&colors.boolean)?;
357 print!("{}", value_str);
358 stdout.reset()?;
359 } else if clean_value.starts_with('"') && clean_value.ends_with('"') {
360 stdout.set_color(&colors.string)?;
362 print!("{}", value_str);
363 stdout.reset()?;
364 } else if clean_value.parse::<f64>().is_ok() {
365 stdout.set_color(&colors.number)?;
367 print!("{}", value_str);
368 stdout.reset()?;
369 } else {
370 print!("{}", value_str);
372 }
373
374 Ok(())
375}
376
377fn print_as_csv(data: &[Value], use_colors: bool) -> Result<(), Error> {
378 if data.is_empty() {
379 return Ok(());
380 }
381
382 let mut all_fields = IndexSet::new();
384 for item in data {
385 collect_flattened_fields_ordered(item, "", &mut all_fields);
386 }
387
388 let fields: Vec<String> = all_fields.into_iter().collect();
389
390 if use_colors {
391 print_colored_csv(data, &fields)?;
392 } else {
393 print_plain_csv(data, &fields);
394 }
395
396 Ok(())
397}
398
399fn print_colored_csv(data: &[Value], fields: &[String]) -> Result<(), Error> {
400 let mut stdout = StandardStream::stdout(ColorChoice::Always);
401 let colors = ColorScheme::new();
402
403 stdout.set_color(&colors.header)?;
405 let header_row = escape_and_join_csv_row(fields);
406 print!("{}", header_row);
407 stdout.reset()?;
408 println!();
409
410 for item in data {
412 let row_values: Vec<String> = fields
413 .iter()
414 .map(|field| get_flattened_value(item, field))
415 .collect();
416
417 print_colored_csv_row(item, fields, &row_values, &mut stdout, &colors)?;
418 }
419
420 Ok(())
421}
422
423fn print_colored_csv_row(
425 item: &Value,
426 fields: &[String],
427 row_values: &[String],
428 stdout: &mut StandardStream,
429 colors: &ColorScheme,
430) -> Result<(), Error> {
431 let mut first = true;
432
433 for (i, value_str) in row_values.iter().enumerate() {
434 if !first {
435 print!(",");
436 }
437 first = false;
438
439 let field = &fields[i];
441 let value = get_field_value_for_coloring(item, field);
442 let color = get_color_for_value(&value, colors);
443
444 stdout.set_color(color)?;
445 let escaped_value = escape_csv_value(value_str);
446 print!("{}", escaped_value);
447 stdout.reset()?;
448 }
449 println!();
450
451 Ok(())
452}
453
454fn print_plain_csv(data: &[Value], fields: &[String]) {
456 print_csv_row(fields);
458
459 for item in data {
461 let row_values: Vec<String> = fields
462 .iter()
463 .map(|field| get_flattened_value(item, field))
464 .collect();
465 print_csv_row(&row_values);
466 }
467}
468
469fn print_csv_row(values: &[String]) {
471 let escaped_values: Vec<String> = values
472 .iter()
473 .map(|value| escape_csv_value(value))
474 .collect();
475
476 println!("{}", escaped_values.join(","));
477}
478
479fn escape_and_join_csv_row(values: &[String]) -> String {
481 let escaped_values: Vec<String> = values
482 .iter()
483 .map(|value| escape_csv_value(value))
484 .collect();
485
486 escaped_values.join(",")
487}
488
489fn escape_csv_value(value: &str) -> String {
491 let needs_quoting = value.contains(',')
498 || value.contains('"')
499 || value.contains('\n')
500 || value.contains('\r')
501 || value.starts_with(' ')
502 || value.ends_with(' ')
503 || value.is_empty(); if needs_quoting {
506 format!("\"{}\"", value.replace('"', "\"\""))
508 } else {
509 value.to_string()
510 }
511}
512
513fn print_as_table(data: &[Value], use_colors: bool) -> Result<(), Error> {
514 if data.is_empty() {
515 return Ok(());
516 }
517
518 let mut all_fields = IndexSet::new();
520 for item in data {
521 collect_flattened_fields_ordered(item, "", &mut all_fields);
522 }
523
524 let fields: Vec<String> = all_fields.into_iter().collect();
525
526 let mut max_widths = vec![0; fields.len()];
528
529 for (i, field) in fields.iter().enumerate() {
531 max_widths[i] = field.len();
532 }
533
534 for item in data {
536 for (i, field) in fields.iter().enumerate() {
537 let value_str = get_flattened_value(item, field);
538 max_widths[i] = max_widths[i].max(value_str.len());
539 }
540 }
541
542 if use_colors {
543 print_colored_table(data, &fields, &max_widths)?;
544 } else {
545 print_plain_table(data, &fields, &max_widths);
546 }
547
548 Ok(())
549}
550
551fn print_colored_table(
552 data: &[Value],
553 fields: &[String],
554 max_widths: &[usize],
555) -> Result<(), Error> {
556 let mut stdout = StandardStream::stdout(ColorChoice::Always);
557 let colors = ColorScheme::new();
558
559 stdout.set_color(&colors.header)?;
561 for (i, field) in fields.iter().enumerate() {
562 print!("{:<width$}", field, width = max_widths[i]);
563 if i < fields.len() - 1 {
564 print!(" ");
565 }
566 }
567 stdout.reset()?;
568 println!();
569
570 for item in data {
572 for (i, field) in fields.iter().enumerate() {
573 let value_str = get_flattened_value(item, field);
574
575 let value = get_field_value_for_coloring(item, field);
577 let color = get_color_for_value(&value, &colors);
578
579 stdout.set_color(color)?;
580 print!("{:<width$}", value_str, width = max_widths[i]);
581 stdout.reset()?;
582
583 if i < fields.len() - 1 {
584 print!(" ");
585 }
586 }
587 println!();
588 }
589
590 Ok(())
591}
592
593fn print_plain_table(data: &[Value], fields: &[String], max_widths: &[usize]) {
594 for (i, field) in fields.iter().enumerate() {
596 print!("{:<width$}", field, width = max_widths[i]);
597 if i < fields.len() - 1 {
598 print!(" ");
599 }
600 }
601 println!();
602
603 for item in data {
605 for (i, field) in fields.iter().enumerate() {
606 let value_str = get_flattened_value(item, field);
607 print!("{:<width$}", value_str, width = max_widths[i]);
608 if i < fields.len() - 1 {
609 print!(" ");
610 }
611 }
612 println!();
613 }
614}
615
616pub fn print_data_info(data: &[Value]) {
617 let use_colors = should_use_colors();
618
619 if use_colors {
620 print_colored_data_info(data).unwrap_or_else(|_| print_plain_data_info(data));
621 } else {
622 print_plain_data_info(data);
623 }
624}
625
626fn print_colored_data_info(data: &[Value]) -> Result<(), Error> {
627 let mut stdout = StandardStream::stdout(ColorChoice::Always);
628 let colors = ColorScheme::new();
629
630 stdout.set_color(&colors.header)?;
632 println!("=== Data Information ===");
633 stdout.reset()?;
634
635 println!("Total records: {}", data.len());
636
637 if data.is_empty() {
638 return Ok(());
639 }
640
641 if data.len() == 1 {
643 let first_item = &data[0];
644 match first_item {
645 Value::Object(obj) => {
646 println!("Type: Single Object"); println!("Fields: {}", obj.len());
648 println!();
649
650 stdout.set_color(&colors.header)?;
652 println!("Field Details:");
653 stdout.reset()?;
654
655 for (key, value) in obj {
656 let field_type = get_value_type_info(value);
657 let sample_value = get_sample_value(value);
658
659 stdout.set_color(&colors.string)?;
661 print!(" {:<15}", key);
662 stdout.reset()?;
663
664 let type_color = get_color_for_value(value, &colors);
666 stdout.set_color(type_color)?;
667 print!(" {:<10}", field_type);
668 stdout.reset()?;
669
670 println!(" (e.g., {})", sample_value);
671 }
672
673 println!();
675 stdout.set_color(&colors.header)?;
676 println!("Array Fields:");
677 stdout.reset()?;
678
679 for (key, value) in obj {
680 if let Value::Array(arr) = value {
681 stdout.set_color(&colors.array_info)?;
682 print!(" {:<15}", key);
683 stdout.reset()?;
684 println!(" [{} items]", arr.len());
685
686 if let Some(first_elem) = arr.first() {
687 if let Value::Object(elem_obj) = first_elem {
688 print!(" └─ ");
689 let sub_fields: Vec<&String> = elem_obj.keys().collect();
690 let sub_fields: Vec<&str> =
691 sub_fields.into_iter().map(|f| f.as_str()).collect();
692 println!("{}", sub_fields.join(", "));
693 }
694 }
695 }
696 }
697 }
698 Value::Array(_) => {
699 println!("Type: Nested Array");
700 }
701 _ => {
702 println!("Type: Single Value");
703 println!("Value: {}", get_sample_value(first_item));
704 }
705 }
706 } else {
707 let first_item = &data[0];
709 match first_item {
710 Value::Object(obj) => {
711 println!("Type: Object Array");
712 println!("Fields: {}", obj.len());
713 println!();
714
715 stdout.set_color(&colors.header)?;
717 println!("Field Details:");
718 stdout.reset()?;
719
720 for (key, value) in obj {
721 let field_type = get_value_type_info(value);
722 let sample_value = get_sample_value(value);
723
724 stdout.set_color(&colors.string)?;
725 print!(" {:<15}", key);
726 stdout.reset()?;
727
728 let type_color = get_color_for_value(value, &colors);
729 stdout.set_color(type_color)?;
730 print!(" {:<10}", field_type);
731 stdout.reset()?;
732
733 println!(" (e.g., {})", sample_value);
734 }
735
736 println!();
737 stdout.set_color(&colors.header)?;
738 println!("Array Fields:");
739 stdout.reset()?;
740
741 for (key, value) in obj {
742 if let Value::Array(arr) = value {
743 stdout.set_color(&colors.array_info)?;
744 print!(" {:<15}", key);
745 stdout.reset()?;
746 println!(" [{} items]", arr.len());
747
748 if let Some(first_elem) = arr.first() {
749 if let Value::Object(elem_obj) = first_elem {
750 print!(" └─ ");
751 let sub_fields: Vec<&String> = elem_obj.keys().collect();
752 let sub_fields: Vec<&str> =
753 sub_fields.into_iter().map(|f| f.as_str()).collect();
754 println!("{}", sub_fields.join(", "));
755 }
756 }
757 }
758 }
759 }
760 _ => {
761 println!("Type: Simple Values");
762 }
763 }
764 }
765
766 Ok(())
767}
768
769fn print_plain_data_info(data: &[Value]) {
770 println!("=== Data Information ===");
771 println!("Total records: {}", data.len());
772
773 if data.is_empty() {
774 return;
775 }
776
777 if data.len() == 1 {
779 let first_item = &data[0];
780 match first_item {
781 Value::Object(obj) => {
782 println!("Type: Single Object"); println!("Fields: {}", obj.len());
784 println!();
785
786 println!("Field Details:");
787 for (key, value) in obj {
788 let field_type = get_value_type_info(value);
789 let sample_value = get_sample_value(value);
790 println!(" {:<15} {:<10} (e.g., {})", key, field_type, sample_value);
791 }
792
793 println!();
794 println!("Array Fields:");
795 for (key, value) in obj {
796 if let Value::Array(arr) = value {
797 println!(" {:<15} [{} items]", key, arr.len());
798 if let Some(first_elem) = arr.first() {
799 if let Value::Object(elem_obj) = first_elem {
800 print!(" └─ ");
801 let sub_fields: Vec<&String> = elem_obj.keys().collect();
802 let sub_fields: Vec<&str> =
803 sub_fields.into_iter().map(|f| f.as_str()).collect();
804 println!("{}", sub_fields.join(", "));
805 }
806 }
807 }
808 }
809 }
810 Value::Array(_) => {
811 println!("Type: Nested Array");
812 }
813 _ => {
814 println!("Type: Single Value");
815 println!("Value: {}", get_sample_value(first_item));
816 }
817 }
818 } else {
819 let first_item = &data[0];
821 match first_item {
822 Value::Object(obj) => {
823 println!("Type: Object Array");
824 println!("Fields: {}", obj.len());
825 println!();
826
827 println!("Field Details:");
828 for (key, value) in obj {
829 let field_type = get_value_type_info(value);
830 let sample_value = get_sample_value(value);
831 println!(" {:<15} {:<10} (e.g., {})", key, field_type, sample_value);
832 }
833
834 println!();
835 println!("Array Fields:");
836 for (key, value) in obj {
837 if let Value::Array(arr) = value {
838 println!(" {:<15} [{} items]", key, arr.len());
839 if let Some(first_elem) = arr.first() {
840 if let Value::Object(elem_obj) = first_elem {
841 print!(" └─ ");
842 let sub_fields: Vec<&String> = elem_obj.keys().collect();
843 let sub_fields: Vec<&str> =
844 sub_fields.into_iter().map(|f| f.as_str()).collect();
845 println!("{}", sub_fields.join(", "));
846 }
847 }
848 }
849 }
850 }
851 Value::Array(_) => {
852 println!("Type: Nested Array");
853 }
854 _ => {
855 println!("Type: Simple Values");
856 }
857 }
858 }
859}