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::Auto => {
100 match analyze_data_structure(data) {
102 DataType::SimpleList => print_as_list(data, use_colors)?,
103 DataType::ObjectArray => print_as_table(data, use_colors)?,
104 DataType::NestedArray => {
105 let flattened = flatten_nested_arrays(data);
106 if is_object_array(&flattened) {
107 print_as_table(&flattened, use_colors)?;
108 } else if is_simple_values(&flattened) {
109 print_as_list(&flattened, use_colors)?;
110 } else {
111 print_as_json(data, use_colors)?;
112 }
113 }
114 DataType::Mixed => print_as_json(data, use_colors)?,
115 }
116 }
117 }
118
119 Ok(())
120}
121
122fn analyze_data_structure(data: &[Value]) -> DataType {
123 if is_simple_values(data) {
124 return DataType::SimpleList;
125 }
126
127 if is_object_array(data) {
128 return DataType::ObjectArray;
129 }
130
131 if data.len() == 1 && data[0].is_array() {
133 return DataType::NestedArray;
134 }
135
136 DataType::Mixed
137}
138
139fn flatten_nested_arrays(data: &[Value]) -> Vec<Value> {
140 let mut flattened = Vec::new();
141
142 for item in data {
143 match item {
144 Value::Array(arr) => {
145 flattened.extend(arr.iter().cloned());
147 }
148 _ => {
149 flattened.push(item.clone());
150 }
151 }
152 }
153
154 flattened
155}
156
157fn collect_flattened_fields_ordered(value: &Value, prefix: &str, fields: &mut IndexSet<String>) {
158 match value {
159 Value::Object(obj) => {
160 for (key, val) in obj {
161 let field_name = if prefix.is_empty() {
163 key.clone()
164 } else {
165 format!("{}.{}", prefix, key)
166 };
167
168 match val {
169 Value::Object(_) => {
170 collect_flattened_fields_ordered(val, &field_name, fields);
171 }
172 _ => {
173 fields.insert(field_name);
174 }
175 }
176 }
177 }
178 _ => {
179 if !prefix.is_empty() {
180 fields.insert(prefix.to_string());
181 }
182 }
183 }
184}
185
186fn get_flattened_value(item: &Value, field_path: &str) -> String {
187 let parts: Vec<&str> = field_path.split('.').collect();
188 let mut current = item;
189
190 for part in parts {
191 match current.get(part) {
192 Some(val) => current = val,
193 None => return "".to_string(),
194 }
195 }
196
197 match current {
198 Value::Array(arr) => {
199 format!("[{} items]", arr.len())
201 }
202 _ => value_to_string(current),
203 }
204}
205
206fn get_field_value_for_coloring(item: &Value, field_path: &str) -> Value {
207 let parts: Vec<&str> = field_path.split('.').collect();
208 let mut current = item;
209
210 for part in parts {
211 match current.get(part) {
212 Some(val) => current = val,
213 None => return Value::Null,
214 }
215 }
216
217 current.clone()
218}
219
220fn get_value_type_info(value: &Value) -> &'static str {
221 match value {
222 Value::String(_) => "String",
223 Value::Number(_) => "Number",
224 Value::Bool(_) => "Boolean",
225 Value::Array(_) => "Array",
226 Value::Object(_) => "Object",
227 Value::Null => "Null",
228 }
229}
230
231fn get_sample_value(value: &Value) -> String {
232 match value {
233 Value::String(s) => format!("\"{}\"", s.chars().take(20).collect::<String>()),
234 Value::Number(n) => n.to_string(),
235 Value::Bool(b) => b.to_string(),
236 Value::Array(arr) => format!("[{} items]", arr.len()),
237 Value::Object(obj) => format!("{{{}...}}", obj.keys().next().unwrap_or(&"".to_string())),
238 Value::Null => "null".to_string(),
239 }
240}
241
242fn is_simple_values(data: &[Value]) -> bool {
243 data.iter()
244 .all(|v| matches!(v, Value::String(_) | Value::Number(_) | Value::Bool(_)))
245}
246
247fn is_object_array(data: &[Value]) -> bool {
248 data.iter().all(|v| v.is_object())
249}
250
251fn print_as_list(data: &[Value], use_colors: bool) -> Result<(), Error> {
252 if use_colors {
253 let mut stdout = StandardStream::stdout(ColorChoice::Always);
254 let colors = ColorScheme::new();
255
256 for item in data {
257 let color = get_color_for_value(item, &colors);
258 stdout.set_color(color)?;
259 print!("{}", value_to_string(item));
260 stdout.reset()?;
261 println!();
262 }
263 } else {
264 data.iter().for_each(|item| {
265 println!("{}", value_to_string(item));
266 });
267 }
268
269 Ok(())
270}
271
272fn print_as_json(data: &[Value], use_colors: bool) -> Result<(), Error> {
273 if use_colors {
274 print_colored_json_simple(data)?;
275 } else {
276 let json = serde_json::to_string_pretty(data).map_err(Error::Json)?;
277 println!("{}", json);
278 }
279
280 Ok(())
281}
282
283fn print_colored_json_simple(data: &[Value]) -> Result<(), Error> {
284 let mut stdout = StandardStream::stdout(ColorChoice::Always);
285 let colors = ColorScheme::new();
286
287 let json = serde_json::to_string_pretty(data).map_err(Error::Json)?;
289
290 for line in json.lines() {
291 let trimmed = line.trim();
292
293 if trimmed.starts_with('"') && trimmed.contains(':') {
295 if let Some(colon_pos) = trimmed.find(':') {
297 let key_part = &trimmed[..colon_pos + 1];
298 let value_part = &trimmed[colon_pos + 1..].trim();
299
300 let indent = &line[..line.len() - line.trim_start().len()];
302 print!("{}", indent);
303
304 stdout.set_color(&colors.header)?;
306 print!("{}", key_part);
307 stdout.reset()?;
308
309 print!(" ");
310
311 print_colored_json_value(value_part, &colors, &mut stdout)?;
313 println!();
314 } else {
315 println!("{}", line);
316 }
317 } else if trimmed.starts_with('{')
318 || trimmed.starts_with('}')
319 || trimmed.starts_with('[')
320 || trimmed.starts_with(']')
321 {
322 let indent = &line[..line.len() - line.trim_start().len()];
324 print!("{}", indent);
325 stdout.set_color(&colors.header)?;
326 print!("{}", trimmed);
327 stdout.reset()?;
328 println!();
329 } else {
330 let indent = &line[..line.len() - line.trim_start().len()];
332 print!("{}", indent);
333 print_colored_json_value(trimmed, &colors, &mut stdout)?;
334 println!();
335 }
336 }
337
338 Ok(())
339}
340
341fn print_colored_json_value(
342 value_str: &str,
343 colors: &ColorScheme,
344 stdout: &mut StandardStream,
345) -> Result<(), Error> {
346 let clean_value = value_str.trim_end_matches(',');
347
348 if clean_value == "null" {
349 stdout.set_color(&colors.null)?;
350 print!("{}", value_str);
351 stdout.reset()?;
352 } else if clean_value == "true" || clean_value == "false" {
353 stdout.set_color(&colors.boolean)?;
354 print!("{}", value_str);
355 stdout.reset()?;
356 } else if clean_value.starts_with('"') && clean_value.ends_with('"') {
357 stdout.set_color(&colors.string)?;
359 print!("{}", value_str);
360 stdout.reset()?;
361 } else if clean_value.parse::<f64>().is_ok() {
362 stdout.set_color(&colors.number)?;
364 print!("{}", value_str);
365 stdout.reset()?;
366 } else {
367 print!("{}", value_str);
369 }
370
371 Ok(())
372}
373
374fn print_as_table(data: &[Value], use_colors: bool) -> Result<(), Error> {
375 if data.is_empty() {
376 return Ok(());
377 }
378
379 let mut all_fields = IndexSet::new();
381 for item in data {
382 collect_flattened_fields_ordered(item, "", &mut all_fields);
383 }
384
385 let fields: Vec<String> = all_fields.into_iter().collect();
386
387 let mut max_widths = vec![0; fields.len()];
389
390 for (i, field) in fields.iter().enumerate() {
392 max_widths[i] = field.len();
393 }
394
395 for item in data {
397 for (i, field) in fields.iter().enumerate() {
398 let value_str = get_flattened_value(item, field);
399 max_widths[i] = max_widths[i].max(value_str.len());
400 }
401 }
402
403 if use_colors {
404 print_colored_table(data, &fields, &max_widths)?;
405 } else {
406 print_plain_table(data, &fields, &max_widths);
407 }
408
409 Ok(())
410}
411
412fn print_colored_table(
413 data: &[Value],
414 fields: &[String],
415 max_widths: &[usize],
416) -> Result<(), Error> {
417 let mut stdout = StandardStream::stdout(ColorChoice::Always);
418 let colors = ColorScheme::new();
419
420 stdout.set_color(&colors.header)?;
422 for (i, field) in fields.iter().enumerate() {
423 print!("{:<width$}", field, width = max_widths[i]);
424 if i < fields.len() - 1 {
425 print!(" ");
426 }
427 }
428 stdout.reset()?;
429 println!();
430
431 for item in data {
433 for (i, field) in fields.iter().enumerate() {
434 let value_str = get_flattened_value(item, field);
435
436 let value = get_field_value_for_coloring(item, field);
438 let color = get_color_for_value(&value, &colors);
439
440 stdout.set_color(color)?;
441 print!("{:<width$}", value_str, width = max_widths[i]);
442 stdout.reset()?;
443
444 if i < fields.len() - 1 {
445 print!(" ");
446 }
447 }
448 println!();
449 }
450
451 Ok(())
452}
453
454fn print_plain_table(data: &[Value], fields: &[String], max_widths: &[usize]) {
455 for (i, field) in fields.iter().enumerate() {
457 print!("{:<width$}", field, width = max_widths[i]);
458 if i < fields.len() - 1 {
459 print!(" ");
460 }
461 }
462 println!();
463
464 for item in data {
466 for (i, field) in fields.iter().enumerate() {
467 let value_str = get_flattened_value(item, field);
468 print!("{:<width$}", value_str, width = max_widths[i]);
469 if i < fields.len() - 1 {
470 print!(" ");
471 }
472 }
473 println!();
474 }
475}
476
477pub fn print_data_info(data: &[Value]) {
478 let use_colors = should_use_colors();
479
480 if use_colors {
481 print_colored_data_info(data).unwrap_or_else(|_| print_plain_data_info(data));
482 } else {
483 print_plain_data_info(data);
484 }
485}
486
487fn print_colored_data_info(data: &[Value]) -> Result<(), Error> {
488 let mut stdout = StandardStream::stdout(ColorChoice::Always);
489 let colors = ColorScheme::new();
490
491 stdout.set_color(&colors.header)?;
493 println!("=== Data Information ===");
494 stdout.reset()?;
495
496 println!("Total records: {}", data.len());
497
498 if data.is_empty() {
499 return Ok(());
500 }
501
502 if data.len() == 1 {
504 let first_item = &data[0];
505 match first_item {
506 Value::Object(obj) => {
507 println!("Type: Single Object"); println!("Fields: {}", obj.len());
509 println!();
510
511 stdout.set_color(&colors.header)?;
513 println!("Field Details:");
514 stdout.reset()?;
515
516 for (key, value) in obj {
517 let field_type = get_value_type_info(value);
518 let sample_value = get_sample_value(value);
519
520 stdout.set_color(&colors.string)?;
522 print!(" {:<15}", key);
523 stdout.reset()?;
524
525 let type_color = get_color_for_value(value, &colors);
527 stdout.set_color(type_color)?;
528 print!(" {:<10}", field_type);
529 stdout.reset()?;
530
531 println!(" (e.g., {})", sample_value);
532 }
533
534 println!();
536 stdout.set_color(&colors.header)?;
537 println!("Array Fields:");
538 stdout.reset()?;
539
540 for (key, value) in obj {
541 if let Value::Array(arr) = value {
542 stdout.set_color(&colors.array_info)?;
543 print!(" {:<15}", key);
544 stdout.reset()?;
545 println!(" [{} items]", arr.len());
546
547 if let Some(first_elem) = arr.first() {
548 if let Value::Object(elem_obj) = first_elem {
549 print!(" └─ ");
550 let sub_fields: Vec<&String> = elem_obj.keys().collect();
551 let sub_fields: Vec<&str> =
552 sub_fields.into_iter().map(|f| f.as_str()).collect();
553 println!("{}", sub_fields.join(", "));
554 }
555 }
556 }
557 }
558 }
559 Value::Array(_) => {
560 println!("Type: Nested Array");
561 }
562 _ => {
563 println!("Type: Single Value");
564 println!("Value: {}", get_sample_value(first_item));
565 }
566 }
567 } else {
568 let first_item = &data[0];
570 match first_item {
571 Value::Object(obj) => {
572 println!("Type: Object Array");
573 println!("Fields: {}", obj.len());
574 println!();
575
576 stdout.set_color(&colors.header)?;
578 println!("Field Details:");
579 stdout.reset()?;
580
581 for (key, value) in obj {
582 let field_type = get_value_type_info(value);
583 let sample_value = get_sample_value(value);
584
585 stdout.set_color(&colors.string)?;
586 print!(" {:<15}", key);
587 stdout.reset()?;
588
589 let type_color = get_color_for_value(value, &colors);
590 stdout.set_color(type_color)?;
591 print!(" {:<10}", field_type);
592 stdout.reset()?;
593
594 println!(" (e.g., {})", sample_value);
595 }
596
597 println!();
598 stdout.set_color(&colors.header)?;
599 println!("Array Fields:");
600 stdout.reset()?;
601
602 for (key, value) in obj {
603 if let Value::Array(arr) = value {
604 stdout.set_color(&colors.array_info)?;
605 print!(" {:<15}", key);
606 stdout.reset()?;
607 println!(" [{} items]", arr.len());
608
609 if let Some(first_elem) = arr.first() {
610 if let Value::Object(elem_obj) = first_elem {
611 print!(" └─ ");
612 let sub_fields: Vec<&String> = elem_obj.keys().collect();
613 let sub_fields: Vec<&str> =
614 sub_fields.into_iter().map(|f| f.as_str()).collect();
615 println!("{}", sub_fields.join(", "));
616 }
617 }
618 }
619 }
620 }
621 _ => {
622 println!("Type: Simple Values");
623 }
624 }
625 }
626
627 Ok(())
628}
629
630fn print_plain_data_info(data: &[Value]) {
631 println!("=== Data Information ===");
632 println!("Total records: {}", data.len());
633
634 if data.is_empty() {
635 return;
636 }
637
638 if data.len() == 1 {
640 let first_item = &data[0];
641 match first_item {
642 Value::Object(obj) => {
643 println!("Type: Single Object"); println!("Fields: {}", obj.len());
645 println!();
646
647 println!("Field Details:");
648 for (key, value) in obj {
649 let field_type = get_value_type_info(value);
650 let sample_value = get_sample_value(value);
651 println!(" {:<15} {:<10} (e.g., {})", key, field_type, sample_value);
652 }
653
654 println!();
655 println!("Array Fields:");
656 for (key, value) in obj {
657 if let Value::Array(arr) = value {
658 println!(" {:<15} [{} items]", key, arr.len());
659 if let Some(first_elem) = arr.first() {
660 if let Value::Object(elem_obj) = first_elem {
661 print!(" └─ ");
662 let sub_fields: Vec<&String> = elem_obj.keys().collect();
663 let sub_fields: Vec<&str> =
664 sub_fields.into_iter().map(|f| f.as_str()).collect();
665 println!("{}", sub_fields.join(", "));
666 }
667 }
668 }
669 }
670 }
671 Value::Array(_) => {
672 println!("Type: Nested Array");
673 }
674 _ => {
675 println!("Type: Single Value");
676 println!("Value: {}", get_sample_value(first_item));
677 }
678 }
679 } else {
680 let first_item = &data[0];
682 match first_item {
683 Value::Object(obj) => {
684 println!("Type: Object Array");
685 println!("Fields: {}", obj.len());
686 println!();
687
688 println!("Field Details:");
689 for (key, value) in obj {
690 let field_type = get_value_type_info(value);
691 let sample_value = get_sample_value(value);
692 println!(" {:<15} {:<10} (e.g., {})", key, field_type, sample_value);
693 }
694
695 println!();
696 println!("Array Fields:");
697 for (key, value) in obj {
698 if let Value::Array(arr) = value {
699 println!(" {:<15} [{} items]", key, arr.len());
700 if let Some(first_elem) = arr.first() {
701 if let Value::Object(elem_obj) = first_elem {
702 print!(" └─ ");
703 let sub_fields: Vec<&String> = elem_obj.keys().collect();
704 let sub_fields: Vec<&str> =
705 sub_fields.into_iter().map(|f| f.as_str()).collect();
706 println!("{}", sub_fields.join(", "));
707 }
708 }
709 }
710 }
711 }
712 Value::Array(_) => {
713 println!("Type: Nested Array");
714 }
715 _ => {
716 println!("Type: Simple Values");
717 }
718 }
719 }
720}