1use serde::Serialize;
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
5pub enum OutputFormat {
6 Table,
7 Json,
8}
9
10impl fmt::Display for OutputFormat {
11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12 match self {
13 OutputFormat::Table => write!(f, "table"),
14 OutputFormat::Json => write!(f, "json"),
15 }
16 }
17}
18
19#[derive(Debug, Serialize)]
20pub struct CommandOutput {
21 pub data: serde_json::Value,
22 pub headers: Vec<String>,
23 pub rows: Vec<Vec<String>>,
24 #[serde(skip)]
25 pub format: OutputFormat,
26 #[serde(skip)]
27 pub addendum: Option<String>,
28 #[serde(skip_serializing_if = "Vec::is_empty")]
29 pub warnings: Vec<String>,
30 #[serde(skip)]
31 #[doc(hidden)]
32 pub suppress_final_output: bool,
33}
34
35impl CommandOutput {
36 pub fn new(
37 data: serde_json::Value,
38 headers: Vec<String>,
39 rows: Vec<Vec<String>>,
40 ) -> Self {
41 Self {
42 data,
43 headers,
44 rows,
45 format: OutputFormat::Table,
46 addendum: None,
47 warnings: vec![],
48 suppress_final_output: false,
49 }
50 }
51
52 pub fn json(data: serde_json::Value) -> Self {
53 Self {
54 data,
55 headers: vec![],
56 rows: vec![],
57 format: OutputFormat::Table,
58 addendum: None,
59 warnings: vec![],
60 suppress_final_output: false,
61 }
62 }
63
64 pub fn new_empty() -> Self {
65 Self {
66 data: serde_json::json!({}),
67 headers: vec![],
68 rows: vec![],
69 format: OutputFormat::Table,
70 addendum: None,
71 warnings: vec![],
72 suppress_final_output: false,
73 }
74 }
75
76 pub fn with_format(mut self, format: OutputFormat) -> Self {
77 self.format = format;
78 self
79 }
80
81 pub fn with_addendum(mut self, addendum: impl Into<String>) -> Self {
82 self.addendum = Some(addendum.into());
83 self
84 }
85
86 pub fn with_warning(mut self, warning: impl Into<String>) -> Self {
87 self.warnings.push(warning.into());
88 self
89 }
90
91 pub fn add_warning(&mut self, warning: impl Into<String>) {
92 self.warnings.push(warning.into());
93 }
94
95 pub fn with_suppress_final_output(mut self, suppress: bool) -> Self {
96 self.suppress_final_output = suppress;
97 self
98 }
99
100 pub fn render(&self) -> String {
101 match self.format {
102 #[cfg(not(target_arch = "wasm32"))]
103 OutputFormat::Table => table::render(self),
104 #[cfg(target_arch = "wasm32")]
105 OutputFormat::Table => json::render(self),
106 OutputFormat::Json => json::render(self),
107 }
108 }
109}
110
111#[cfg(not(target_arch = "wasm32"))]
112pub mod table;
113pub mod json;
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use serde_json::json;
119
120 #[test]
121 fn test_output_format_display_table() {
122 assert_eq!(format!("{}", OutputFormat::Table), "table");
123 }
124
125 #[test]
126 fn test_output_format_display_json() {
127 assert_eq!(format!("{}", OutputFormat::Json), "json");
128 }
129
130 #[test]
131 fn test_output_format_from_clap() {
132 let formats = [OutputFormat::Table, OutputFormat::Json];
134 assert_eq!(formats.len(), 2);
135 }
136
137 #[test]
138 fn test_command_output_new() {
139 let data = json!({"key": "value"});
140 let headers = vec!["H1".into(), "H2".into()];
141 let rows = vec![vec!["a".into(), "b".into()]];
142 let output = CommandOutput::new(data.clone(), headers.clone(), rows.clone());
143
144 assert_eq!(output.data, data);
145 assert_eq!(output.headers, headers);
146 assert_eq!(output.rows, rows);
147 assert_eq!(output.format, OutputFormat::Table);
148 assert!(output.addendum.is_none());
149 }
150
151 #[test]
152 fn test_command_output_json() {
153 let data = json!({"test": 123});
154 let output = CommandOutput::json(data.clone());
155
156 assert_eq!(output.data, data);
157 assert!(output.headers.is_empty());
158 assert!(output.rows.is_empty());
159 assert_eq!(output.format, OutputFormat::Table);
160 }
161
162 #[test]
163 fn test_command_output_new_empty() {
164 let output = CommandOutput::new_empty();
165
166 assert_eq!(output.data, json!({}));
167 assert!(output.headers.is_empty());
168 assert!(output.rows.is_empty());
169 }
170
171 #[test]
172 fn test_command_output_with_format() {
173 let output = CommandOutput::json(json!({"test": true}))
174 .with_format(OutputFormat::Json);
175
176 assert_eq!(output.format, OutputFormat::Json);
177 }
178
179 #[test]
180 fn test_command_output_with_addendum() {
181 let output = CommandOutput::new_empty()
182 .with_addendum("test addendum");
183
184 assert_eq!(output.addendum, Some("test addendum".into()));
185 }
186
187 #[test]
188 fn test_command_output_render_table() {
189 let output = CommandOutput {
190 data: json!({"key": "value"}),
191 headers: vec!["Key".into(), "Value".into()],
192 rows: vec![vec!["key".into(), "value".into()]],
193 format: OutputFormat::Table,
194 addendum: None,
195 warnings: vec![],
196 suppress_final_output: false,
197 };
198
199 let rendered = output.render();
200 assert!(!rendered.is_empty());
201 }
202
203 #[test]
204 fn test_command_output_render_json() {
205 let data = json!({"hello": "world"});
206 let output = CommandOutput::json(data.clone()).with_format(OutputFormat::Json);
207
208 let rendered = output.render();
209 let parsed: serde_json::Value = serde_json::from_str(&rendered).unwrap();
210 assert_eq!(parsed["data"], data);
211 }
212
213 #[test]
214 fn test_command_output_render_json_empty() {
215 let output = CommandOutput::new_empty().with_format(OutputFormat::Json);
216 let rendered = output.render();
217 let parsed: serde_json::Value = serde_json::from_str(&rendered).unwrap();
218 assert_eq!(parsed["data"], json!({}));
219 }
220
221 #[test]
222 fn test_command_output_render_table_with_addendum() {
223 let output = CommandOutput {
224 data: json!({}),
225 headers: vec![],
226 rows: vec![],
227 format: OutputFormat::Table,
228 addendum: Some("Extra info".into()),
229 warnings: vec![],
230 suppress_final_output: false,
231 };
232
233 let rendered = output.render();
234 assert!(rendered.contains("Extra info"));
235 }
236
237 #[test]
238 fn test_command_output_serialization() {
239 let output = CommandOutput {
240 data: json!({"test": true}),
241 headers: vec!["H".into()],
242 rows: vec![vec!["v".into()]],
243 format: OutputFormat::Json,
244 addendum: Some("info".into()),
245 warnings: vec![],
246 suppress_final_output: false,
247 };
248
249 let serialized = serde_json::to_string(&output).unwrap();
250 let deserialized: serde_json::Value = serde_json::from_str(&serialized).unwrap();
251
252 assert_eq!(deserialized["data"]["test"], true);
253 assert_eq!(deserialized["headers"][0], "H");
254 assert_eq!(deserialized["rows"][0][0], "v");
255 }
257
258 #[test]
259 fn test_command_output_render_empty_table_with_data() {
260 let output = CommandOutput {
261 data: json!({"key": "value"}),
262 headers: vec![],
263 rows: vec![],
264 format: OutputFormat::Table,
265 addendum: None,
266 warnings: vec![],
267 suppress_final_output: false,
268 };
269
270 let rendered = output.render();
271 assert!(rendered.contains("key") || rendered.contains("value") || rendered.is_empty());
273 }
274}