1use indexmap::IndexSet;
2use serde_json::Value;
3use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
4
5use crate::{Error, OutputFormat, value_to_string};
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())
56 && std::env::var("NO_COLOR").is_err()
57}
58
59fn get_color_for_value<'a>(value: &Value, colors: &'a ColorScheme) -> &'a ColorSpec {
61 match value {
62 Value::Number(_) => &colors.number,
63 Value::Bool(_) => &colors.boolean,
64 Value::Null => &colors.null,
65 _ => &colors.string,
66 }
67}
68
69pub fn format_output(data: &[Value], format: OutputFormat) -> Result<(), Error> {
70 if data.is_empty() {
71 return Ok(());
72 }
73
74 let use_colors = should_use_colors();
75
76 match format {
77 OutputFormat::Json => {
78 print_as_json(data, use_colors)?;
80 }
81 OutputFormat::Table => {
82 if is_object_array(data) {
84 print_as_table(data, use_colors)?;
85 } else {
86 let flattened = flatten_nested_arrays(data);
87 if is_object_array(&flattened) {
88 print_as_table(&flattened, use_colors)?;
89 } else {
90 return Err(Error::InvalidQuery(
91 "Cannot display as table: data is not object array".into(),
92 ));
93 }
94 }
95 }
96 OutputFormat::List => {
97 print_as_list(data, use_colors)?;
99 }
100 OutputFormat::Auto => {
101 match analyze_data_structure(data) {
103 DataType::SimpleList => print_as_list(data, use_colors)?,
104 DataType::ObjectArray => print_as_table(data, use_colors)?,
105 DataType::NestedArray => {
106 let flattened = flatten_nested_arrays(data);
107 if is_object_array(&flattened) {
108 print_as_table(&flattened, use_colors)?;
109 } else if is_simple_values(&flattened) {
110 print_as_list(&flattened, use_colors)?;
111 } else {
112 print_as_json(data, use_colors)?;
113 }
114 }
115 DataType::Mixed => print_as_json(data, use_colors)?,
116 }
117 }
118 }
119
120 Ok(())
121}
122
123fn analyze_data_structure(data: &[Value]) -> DataType {
124 if is_simple_values(data) {
125 return DataType::SimpleList;
126 }
127
128 if is_object_array(data) {
129 return DataType::ObjectArray;
130 }
131
132 if data.len() == 1 && data[0].is_array() {
134 return DataType::NestedArray;
135 }
136
137 DataType::Mixed
138}
139
140fn flatten_nested_arrays(data: &[Value]) -> Vec<Value> {
141 let mut flattened = Vec::new();
142
143 for item in data {
144 match item {
145 Value::Array(arr) => {
146 flattened.extend(arr.iter().cloned());
148 }
149 _ => {
150 flattened.push(item.clone());
151 }
152 }
153 }
154
155 flattened
156}
157
158fn collect_flattened_fields_ordered(value: &Value, prefix: &str, fields: &mut IndexSet<String>) {
159 match value {
160 Value::Object(obj) => {
161 for (key, val) in obj {
162 let field_name = if prefix.is_empty() {
164 key.clone()
165 } else {
166 format!("{}.{}", prefix, key)
167 };
168
169 match val {
170 Value::Object(_) => {
171 collect_flattened_fields_ordered(val, &field_name, fields);
172 }
173 _ => {
174 fields.insert(field_name);
175 }
176 }
177 }
178 }
179 _ => {
180 if !prefix.is_empty() {
181 fields.insert(prefix.to_string());
182 }
183 }
184 }
185}
186
187fn get_flattened_value(item: &Value, field_path: &str) -> String {
188 let parts: Vec<&str> = field_path.split('.').collect();
189 let mut current = item;
190
191 for part in parts {
192 match current.get(part) {
193 Some(val) => current = val,
194 None => return "".to_string(),
195 }
196 }
197
198 match current {
199 Value::Array(arr) => {
200 format!("[{} items]", arr.len())
202 }
203 _ => value_to_string(current),
204 }
205}
206
207fn get_field_value_for_coloring(item: &Value, field_path: &str) -> Value {
208 let parts: Vec<&str> = field_path.split('.').collect();
209 let mut current = item;
210
211 for part in parts {
212 match current.get(part) {
213 Some(val) => current = val,
214 None => return Value::Null,
215 }
216 }
217
218 current.clone()
219}
220
221fn get_value_type_info(value: &Value) -> &'static str {
222 match value {
223 Value::String(_) => "String",
224 Value::Number(_) => "Number",
225 Value::Bool(_) => "Boolean",
226 Value::Array(_) => "Array",
227 Value::Object(_) => "Object",
228 Value::Null => "Null",
229 }
230}
231
232fn get_sample_value(value: &Value) -> String {
233 match value {
234 Value::String(s) => format!("\"{}\"", s.chars().take(20).collect::<String>()),
235 Value::Number(n) => n.to_string(),
236 Value::Bool(b) => b.to_string(),
237 Value::Array(arr) => format!("[{} items]", arr.len()),
238 Value::Object(obj) => format!("{{{}...}}", obj.keys().next().unwrap_or(&"".to_string())),
239 Value::Null => "null".to_string(),
240 }
241}
242
243fn is_simple_values(data: &[Value]) -> bool {
244 data.iter()
245 .all(|v| matches!(v, Value::String(_) | Value::Number(_) | Value::Bool(_)))
246}
247
248fn is_object_array(data: &[Value]) -> bool {
249 data.iter().all(|v| v.is_object())
250}
251
252fn print_as_list(data: &[Value], use_colors: bool) -> Result<(), Error> {
253 if use_colors {
254 let mut stdout = StandardStream::stdout(ColorChoice::Always);
255 let colors = ColorScheme::new();
256
257 for item in data {
258 let color = get_color_for_value(item, &colors);
259 stdout.set_color(color)?;
260 print!("{}", value_to_string(item));
261 stdout.reset()?;
262 println!();
263 }
264 } else {
265 data.iter().for_each(|item| {
266 println!("{}", value_to_string(item));
267 });
268 }
269
270 Ok(())
271}
272
273fn print_as_json(data: &[Value], use_colors: bool) -> Result<(), Error> {
274 if use_colors {
275 print_colored_json_simple(data)?;
276 } else {
277 let json = serde_json::to_string_pretty(data).map_err(Error::Json)?;
278 println!("{}", json);
279 }
280
281 Ok(())
282}
283
284fn print_colored_json_simple(data: &[Value]) -> Result<(), Error> {
285 let mut stdout = StandardStream::stdout(ColorChoice::Always);
286 let colors = ColorScheme::new();
287
288 let json = serde_json::to_string_pretty(data).map_err(Error::Json)?;
290
291 for line in json.lines() {
292 let trimmed = line.trim();
293
294 if trimmed.starts_with('"') && trimmed.contains(':') {
296 if let Some(colon_pos) = trimmed.find(':') {
298 let key_part = &trimmed[..colon_pos + 1];
299 let value_part = &trimmed[colon_pos + 1..].trim();
300
301 let indent = &line[..line.len() - line.trim_start().len()];
303 print!("{}", indent);
304
305 stdout.set_color(&colors.header)?;
307 print!("{}", key_part);
308 stdout.reset()?;
309
310 print!(" ");
311
312 print_colored_json_value(value_part, &colors, &mut stdout)?;
314 println!();
315 } else {
316 println!("{}", line);
317 }
318 } else if trimmed.starts_with('{') || trimmed.starts_with('}') ||
319 trimmed.starts_with('[') || trimmed.starts_with(']') {
320 let indent = &line[..line.len() - line.trim_start().len()];
322 print!("{}", indent);
323 stdout.set_color(&colors.header)?;
324 print!("{}", trimmed);
325 stdout.reset()?;
326 println!();
327 } else {
328 let indent = &line[..line.len() - line.trim_start().len()];
330 print!("{}", indent);
331 print_colored_json_value(trimmed, &colors, &mut stdout)?;
332 println!();
333 }
334 }
335
336 Ok(())
337}
338
339fn print_colored_json_value(value_str: &str, colors: &ColorScheme, stdout: &mut StandardStream) -> Result<(), Error> {
340 let clean_value = value_str.trim_end_matches(',');
341
342 if clean_value == "null" {
343 stdout.set_color(&colors.null)?;
344 print!("{}", value_str);
345 stdout.reset()?;
346 } else if clean_value == "true" || clean_value == "false" {
347 stdout.set_color(&colors.boolean)?;
348 print!("{}", value_str);
349 stdout.reset()?;
350 } else if clean_value.starts_with('"') && clean_value.ends_with('"') {
351 stdout.set_color(&colors.string)?;
353 print!("{}", value_str);
354 stdout.reset()?;
355 } else if clean_value.parse::<f64>().is_ok() {
356 stdout.set_color(&colors.number)?;
358 print!("{}", value_str);
359 stdout.reset()?;
360 } else {
361 print!("{}", value_str);
363 }
364
365 Ok(())
366}
367
368fn print_as_table(data: &[Value], use_colors: bool) -> Result<(), Error> {
369 if data.is_empty() {
370 return Ok(());
371 }
372
373 let mut all_fields = IndexSet::new();
375 for item in data {
376 collect_flattened_fields_ordered(item, "", &mut all_fields);
377 }
378
379 let fields: Vec<String> = all_fields.into_iter().collect();
380
381 let mut max_widths = vec![0; fields.len()];
383
384 for (i, field) in fields.iter().enumerate() {
386 max_widths[i] = field.len();
387 }
388
389 for item in data {
391 for (i, field) in fields.iter().enumerate() {
392 let value_str = get_flattened_value(item, field);
393 max_widths[i] = max_widths[i].max(value_str.len());
394 }
395 }
396
397 if use_colors {
398 print_colored_table(data, &fields, &max_widths)?;
399 } else {
400 print_plain_table(data, &fields, &max_widths);
401 }
402
403 Ok(())
404}
405
406fn print_colored_table(data: &[Value], fields: &[String], max_widths: &[usize]) -> Result<(), Error> {
407 let mut stdout = StandardStream::stdout(ColorChoice::Always);
408 let colors = ColorScheme::new();
409
410 stdout.set_color(&colors.header)?;
412 for (i, field) in fields.iter().enumerate() {
413 print!("{:<width$}", field, width = max_widths[i]);
414 if i < fields.len() - 1 {
415 print!(" ");
416 }
417 }
418 stdout.reset()?;
419 println!();
420
421 for item in data {
423 for (i, field) in fields.iter().enumerate() {
424 let value_str = get_flattened_value(item, field);
425
426 let value = get_field_value_for_coloring(item, field);
428 let color = get_color_for_value(&value, &colors);
429
430 stdout.set_color(color)?;
431 print!("{:<width$}", value_str, width = max_widths[i]);
432 stdout.reset()?;
433
434 if i < fields.len() - 1 {
435 print!(" ");
436 }
437 }
438 println!();
439 }
440
441 Ok(())
442}
443
444fn print_plain_table(data: &[Value], fields: &[String], max_widths: &[usize]) {
445 for (i, field) in fields.iter().enumerate() {
447 print!("{:<width$}", field, width = max_widths[i]);
448 if i < fields.len() - 1 {
449 print!(" ");
450 }
451 }
452 println!();
453
454 for item in data {
456 for (i, field) in fields.iter().enumerate() {
457 let value_str = get_flattened_value(item, field);
458 print!("{:<width$}", value_str, width = max_widths[i]);
459 if i < fields.len() - 1 {
460 print!(" ");
461 }
462 }
463 println!();
464 }
465}
466
467pub fn print_data_info(data: &[Value]) {
468 let use_colors = should_use_colors();
469
470 if use_colors {
471 print_colored_data_info(data).unwrap_or_else(|_| print_plain_data_info(data));
472 } else {
473 print_plain_data_info(data);
474 }
475}
476
477fn print_colored_data_info(data: &[Value]) -> Result<(), Error> {
478 let mut stdout = StandardStream::stdout(ColorChoice::Always);
479 let colors = ColorScheme::new();
480
481 stdout.set_color(&colors.header)?;
483 println!("=== Data Information ===");
484 stdout.reset()?;
485
486 println!("Total records: {}", data.len());
487
488 if data.is_empty() {
489 return Ok(());
490 }
491
492 let first_item = &data[0];
494 match first_item {
495 Value::Object(obj) => {
496 println!("Type: Object Array");
497 println!("Fields: {}", obj.len());
498 println!();
499
500 stdout.set_color(&colors.header)?;
502 println!("Field Details:");
503 stdout.reset()?;
504
505 for (key, value) in obj {
506 let field_type = get_value_type_info(value);
507 let sample_value = get_sample_value(value);
508
509 stdout.set_color(&colors.string)?;
511 print!(" {:<15}", key);
512 stdout.reset()?;
513
514 let type_color = get_color_for_value(value, &colors);
516 stdout.set_color(type_color)?;
517 print!(" {:<10}", field_type);
518 stdout.reset()?;
519
520 println!(" (e.g., {})", sample_value);
521 }
522
523 println!();
525 stdout.set_color(&colors.header)?;
526 println!("Array Fields:");
527 stdout.reset()?;
528
529 for (key, value) in obj {
530 if let Value::Array(arr) = value {
531 stdout.set_color(&colors.array_info)?;
532 print!(" {:<15}", key);
533 stdout.reset()?;
534 println!(" [{} items]", arr.len());
535
536 if let Some(first_elem) = arr.first() {
537 if let Value::Object(elem_obj) = first_elem {
538 print!(" └─ ");
539 let sub_fields: Vec<&String> = elem_obj.keys().collect();
540 let sub_fields: Vec<&str> =
541 sub_fields.into_iter().map(|f| f.as_str()).collect();
542 println!("{}", sub_fields.join(", "));
543 }
544 }
545 }
546 }
547 }
548 Value::Array(_) => {
549 println!("Type: Nested Array");
550 }
552 _ => {
553 println!("Type: Simple Values");
554 }
556 }
557
558 Ok(())
559}
560
561fn print_plain_data_info(data: &[Value]) {
562 println!("=== Data Information ===");
563 println!("Total records: {}", data.len());
564
565 if data.is_empty() {
566 return;
567 }
568
569 let first_item = &data[0];
571 match first_item {
572 Value::Object(obj) => {
573 println!("Type: Object Array");
574 println!("Fields: {}", obj.len());
575 println!();
576
577 println!("Field Details:");
579 for (key, value) in obj {
580 let field_type = get_value_type_info(value);
581 let sample_value = get_sample_value(value);
582 println!(" {:<15} {:<10} (e.g., {})", key, field_type, sample_value);
583 }
584
585 println!();
587 println!("Array Fields:");
588 for (key, value) in obj {
589 if let Value::Array(arr) = value {
590 println!(" {:<15} [{} items]", key, arr.len());
591 if let Some(first_elem) = arr.first() {
592 if let Value::Object(elem_obj) = first_elem {
593 print!(" └─ ");
594 let sub_fields: Vec<&String> = elem_obj.keys().collect();
595 let sub_fields: Vec<&str> =
596 sub_fields.into_iter().map(|f| f.as_str()).collect();
597 println!("{}", sub_fields.join(", "));
598 }
599 }
600 }
601 }
602 }
603 Value::Array(_) => {
604 println!("Type: Nested Array");
605 }
607 _ => {
608 println!("Type: Simple Values");
609 }
611 }
612}