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
63#[allow(dead_code)]
64pub fn print_table(rows: &[(&str, &str)]) {
65 let stdout = io::stdout();
66 let mut out = stdout.lock();
67 for (key, value) in rows {
68 let _ = writeln!(out, " {key}: {value}");
69 }
70}
71
72pub fn print_value(value: &Value, format: OutputFormat) {
73 match format {
74 OutputFormat::Json => print_json(value),
75 OutputFormat::Table => {
76 if let Some(obj) = value.as_object() {
77 for (k, v) in obj {
78 let display = match v {
79 Value::String(s) => s.clone(),
80 Value::Null => "n/a".to_string(),
81 other => other.to_string(),
82 };
83 println!(" {k}: {display}");
84 }
85 } else {
86 println!("{}", value);
87 }
88 }
89 }
90}
91
92#[allow(dead_code)]
93pub fn print_section(title: &str, value: &Value, format: OutputFormat) {
94 match format {
95 OutputFormat::Json => {} OutputFormat::Table => {
97 println!("{title}:");
98 print_value(value, format);
99 println!();
100 }
101 }
102}