Skip to main content

virtuoso_cli/
output.rs

1use serde::Serialize;
2use serde_json::Value;
3use std::io::{self, IsTerminal, Write};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum OutputFormat {
7    Json,
8    Table,
9}
10
11impl OutputFormat {
12    pub fn resolve(explicit: Option<&str>) -> Self {
13        match explicit {
14            Some("json") => OutputFormat::Json,
15            Some("table") => OutputFormat::Table,
16            Some(_) => OutputFormat::Table,
17            None => {
18                if io::stdout().is_terminal() {
19                    OutputFormat::Table
20                } else {
21                    OutputFormat::Json
22                }
23            }
24        }
25    }
26}
27
28#[derive(Debug, Serialize)]
29pub struct CliError {
30    pub error: String,
31    pub message: String,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub suggestion: Option<String>,
34    pub retryable: bool,
35}
36
37impl CliError {
38    pub fn print(&self, format: OutputFormat) {
39        let stderr = io::stderr();
40        let mut out = stderr.lock();
41        match format {
42            OutputFormat::Json => {
43                let _ = serde_json::to_writer(&mut out, self);
44                let _ = writeln!(out);
45            }
46            OutputFormat::Table => {
47                let _ = writeln!(out, "error: {}", self.message);
48                if let Some(ref suggestion) = self.suggestion {
49                    let _ = writeln!(out, "suggestion: {suggestion}");
50                }
51            }
52        }
53    }
54}
55
56pub fn print_json(value: &Value) {
57    let stdout = io::stdout();
58    let mut out = stdout.lock();
59    let _ = serde_json::to_writer_pretty(&mut out, value);
60    let _ = writeln!(out);
61}
62
63pub fn print_table(rows: &[(&str, &str)]) {
64    let stdout = io::stdout();
65    let mut out = stdout.lock();
66    for (key, value) in rows {
67        let _ = writeln!(out, "  {key}: {value}");
68    }
69}
70
71pub fn print_value(value: &Value, format: OutputFormat) {
72    match format {
73        OutputFormat::Json => print_json(value),
74        OutputFormat::Table => {
75            if let Some(obj) = value.as_object() {
76                for (k, v) in obj {
77                    let display = match v {
78                        Value::String(s) => s.clone(),
79                        Value::Null => "n/a".to_string(),
80                        other => other.to_string(),
81                    };
82                    println!("  {k}: {display}");
83                }
84            } else {
85                println!("{}", value);
86            }
87        }
88    }
89}
90
91pub fn print_section(title: &str, value: &Value, format: OutputFormat) {
92    match format {
93        OutputFormat::Json => {} // JSON mode: caller aggregates into single object
94        OutputFormat::Table => {
95            println!("{title}:");
96            print_value(value, format);
97            println!();
98        }
99    }
100}