Skip to main content

redis_common/
output.rs

1use anyhow::{Context, Result};
2use comfy_table::Table;
3use jmespath::compile;
4use serde::Serialize;
5use serde_json::Value;
6
7#[derive(Debug, Clone, Copy, clap::ValueEnum)]
8pub enum OutputFormat {
9    Json,
10    Yaml,
11    Table,
12}
13
14impl Default for OutputFormat {
15    fn default() -> Self {
16        Self::Json
17    }
18}
19
20pub fn print_output<T: Serialize>(
21    data: T,
22    format: OutputFormat,
23    query: Option<&str>,
24) -> Result<()> {
25    let mut json_value = serde_json::to_value(data)?;
26
27    // Apply JMESPath query if provided
28    if let Some(query_str) = query {
29        let expr = compile(query_str).context("Invalid JMESPath expression")?;
30        // Convert Value to string then parse as Variable
31        let json_str = serde_json::to_string(&json_value)?;
32        let data = jmespath::Variable::from_json(&json_str)
33            .map_err(|e| anyhow::anyhow!("Failed to parse JSON for JMESPath: {}", e))?;
34        let result = expr.search(&data).context("JMESPath query failed")?;
35        // Convert result back to JSON string then parse as Value
36        let result_str = result.to_string();
37        json_value =
38            serde_json::from_str(&result_str).context("Failed to parse JMESPath result")?;
39    }
40
41    match format {
42        OutputFormat::Json => {
43            println!("{}", serde_json::to_string_pretty(&json_value)?);
44        }
45        OutputFormat::Yaml => {
46            println!("{}", serde_yaml::to_string(&json_value)?);
47        }
48        OutputFormat::Table => {
49            print_as_table(&json_value)?;
50        }
51    }
52
53    Ok(())
54}
55
56fn print_as_table(value: &Value) -> Result<()> {
57    match value {
58        Value::Array(arr) if !arr.is_empty() => {
59            let mut table = Table::new();
60
61            // Get headers from first object
62            if let Value::Object(first) = &arr[0] {
63                let headers: Vec<String> = first.keys().cloned().collect();
64                table.set_header(&headers);
65
66                // Add rows
67                for item in arr {
68                    if let Value::Object(obj) = item {
69                        let row: Vec<String> = headers
70                            .iter()
71                            .map(|h| format_value(obj.get(h).unwrap_or(&Value::Null)))
72                            .collect();
73                        table.add_row(row);
74                    }
75                }
76            } else {
77                // Simple array of values
78                table.set_header(vec!["Value"]);
79                for item in arr {
80                    table.add_row(vec![format_value(item)]);
81                }
82            }
83
84            println!("{}", table);
85        }
86        Value::Object(obj) => {
87            let mut table = Table::new();
88            table.set_header(vec!["Key", "Value"]);
89
90            for (key, val) in obj {
91                table.add_row(vec![key.clone(), format_value(val)]);
92            }
93
94            println!("{}", table);
95        }
96        _ => {
97            println!("{}", format_value(value));
98        }
99    }
100
101    Ok(())
102}
103
104fn format_value(value: &Value) -> String {
105    match value {
106        Value::Null => "null".to_string(),
107        Value::Bool(b) => b.to_string(),
108        Value::Number(n) => n.to_string(),
109        Value::String(s) => s.clone(),
110        Value::Array(arr) => format!("[{} items]", arr.len()),
111        Value::Object(obj) => format!("{{{} fields}}", obj.len()),
112    }
113}