Skip to main content

tap_cli/
output.rs

1use serde::Serialize;
2use serde_json::Value;
3
4/// Output format for CLI responses
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum OutputFormat {
7    Json,
8    Text,
9}
10
11impl std::str::FromStr for OutputFormat {
12    type Err = String;
13    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
14        match s.to_lowercase().as_str() {
15            "json" => Ok(Self::Json),
16            "text" => Ok(Self::Text),
17            _ => Err(format!("Unknown format: {}. Use 'json' or 'text'", s)),
18        }
19    }
20}
21
22/// Wrapper for consistent CLI output
23#[derive(Debug, Serialize)]
24struct SuccessEnvelope<T: Serialize> {
25    status: &'static str,
26    data: T,
27}
28
29#[derive(Debug, Serialize)]
30struct ErrorEnvelope {
31    status: &'static str,
32    error: String,
33}
34
35/// Print a successful result in the chosen format
36pub fn print_success<T: Serialize>(format: OutputFormat, data: &T) {
37    match format {
38        OutputFormat::Json => {
39            let envelope = SuccessEnvelope {
40                status: "success",
41                data,
42            };
43            println!(
44                "{}",
45                serde_json::to_string_pretty(&envelope).unwrap_or_else(|e| format!(
46                    "{{\"status\":\"error\",\"error\":\"Serialization failed: {}\"}}",
47                    e
48                ))
49            );
50        }
51        OutputFormat::Text => {
52            // For text mode, pretty-print the data as JSON for now
53            // Individual commands can override with custom formatting
54            let json = serde_json::to_value(data).unwrap_or(Value::Null);
55            print_text_value(&json, 0);
56        }
57    }
58}
59
60/// Print an error in the chosen format
61pub fn print_error(format: OutputFormat, error: &str) {
62    match format {
63        OutputFormat::Json => {
64            let envelope = ErrorEnvelope {
65                status: "error",
66                error: error.to_string(),
67            };
68            eprintln!(
69                "{}",
70                serde_json::to_string_pretty(&envelope).unwrap_or_else(|e| format!(
71                    "{{\"status\":\"error\",\"error\":\"Serialization failed: {}\"}}",
72                    e
73                ))
74            );
75        }
76        OutputFormat::Text => {
77            eprintln!("Error: {}", error);
78        }
79    }
80}
81
82/// Recursively print a JSON value in human-readable text format
83fn print_text_value(value: &Value, indent: usize) {
84    let pad = " ".repeat(indent);
85    match value {
86        Value::Object(map) => {
87            for (key, val) in map {
88                match val {
89                    Value::Object(_) | Value::Array(_) => {
90                        println!("{}{}:", pad, key);
91                        print_text_value(val, indent + 2);
92                    }
93                    _ => {
94                        println!("{}{}: {}", pad, key, format_scalar(val));
95                    }
96                }
97            }
98        }
99        Value::Array(arr) => {
100            for (i, val) in arr.iter().enumerate() {
101                println!("{}[{}]:", pad, i);
102                print_text_value(val, indent + 2);
103            }
104        }
105        _ => {
106            println!("{}{}", pad, format_scalar(value));
107        }
108    }
109}
110
111fn format_scalar(value: &Value) -> String {
112    match value {
113        Value::String(s) => s.clone(),
114        Value::Number(n) => n.to_string(),
115        Value::Bool(b) => b.to_string(),
116        Value::Null => "null".to_string(),
117        _ => value.to_string(),
118    }
119}