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 => {} OutputFormat::Table => {
95 println!("{title}:");
96 print_value(value, format);
97 println!();
98 }
99 }
100}