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 OutputFormat::Table => table::render(self),
103 OutputFormat::Json => json::render(self),
104 }
105 }
106}
107
108pub mod table;
109pub mod json;
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use serde_json::json;
115
116 #[test]
117 fn test_output_format_display_table() {
118 assert_eq!(format!("{}", OutputFormat::Table), "table");
119 }
120
121 #[test]
122 fn test_output_format_display_json() {
123 assert_eq!(format!("{}", OutputFormat::Json), "json");
124 }
125
126 #[test]
127 fn test_output_format_from_clap() {
128 let formats = [OutputFormat::Table, OutputFormat::Json];
130 assert_eq!(formats.len(), 2);
131 }
132
133 #[test]
134 fn test_command_output_new() {
135 let data = json!({"key": "value"});
136 let headers = vec!["H1".into(), "H2".into()];
137 let rows = vec![vec!["a".into(), "b".into()]];
138 let output = CommandOutput::new(data.clone(), headers.clone(), rows.clone());
139
140 assert_eq!(output.data, data);
141 assert_eq!(output.headers, headers);
142 assert_eq!(output.rows, rows);
143 assert_eq!(output.format, OutputFormat::Table);
144 assert!(output.addendum.is_none());
145 }
146
147 #[test]
148 fn test_command_output_json() {
149 let data = json!({"test": 123});
150 let output = CommandOutput::json(data.clone());
151
152 assert_eq!(output.data, data);
153 assert!(output.headers.is_empty());
154 assert!(output.rows.is_empty());
155 assert_eq!(output.format, OutputFormat::Table);
156 }
157
158 #[test]
159 fn test_command_output_new_empty() {
160 let output = CommandOutput::new_empty();
161
162 assert_eq!(output.data, json!({}));
163 assert!(output.headers.is_empty());
164 assert!(output.rows.is_empty());
165 }
166
167 #[test]
168 fn test_command_output_with_format() {
169 let output = CommandOutput::json(json!({"test": true}))
170 .with_format(OutputFormat::Json);
171
172 assert_eq!(output.format, OutputFormat::Json);
173 }
174
175 #[test]
176 fn test_command_output_with_addendum() {
177 let output = CommandOutput::new_empty()
178 .with_addendum("test addendum");
179
180 assert_eq!(output.addendum, Some("test addendum".into()));
181 }
182
183 #[test]
184 fn test_command_output_render_table() {
185 let output = CommandOutput {
186 data: json!({"key": "value"}),
187 headers: vec!["Key".into(), "Value".into()],
188 rows: vec![vec!["key".into(), "value".into()]],
189 format: OutputFormat::Table,
190 addendum: None,
191 warnings: vec![],
192 suppress_final_output: false,
193 };
194
195 let rendered = output.render();
196 assert!(!rendered.is_empty());
197 }
198
199 #[test]
200 fn test_command_output_render_json() {
201 let data = json!({"hello": "world"});
202 let output = CommandOutput::json(data.clone()).with_format(OutputFormat::Json);
203
204 let rendered = output.render();
205 let parsed: serde_json::Value = serde_json::from_str(&rendered).unwrap();
206 assert_eq!(parsed["data"], data);
207 }
208
209 #[test]
210 fn test_command_output_render_json_empty() {
211 let output = CommandOutput::new_empty().with_format(OutputFormat::Json);
212 let rendered = output.render();
213 let parsed: serde_json::Value = serde_json::from_str(&rendered).unwrap();
214 assert_eq!(parsed["data"], json!({}));
215 }
216
217 #[test]
218 fn test_command_output_render_table_with_addendum() {
219 let output = CommandOutput {
220 data: json!({}),
221 headers: vec![],
222 rows: vec![],
223 format: OutputFormat::Table,
224 addendum: Some("Extra info".into()),
225 warnings: vec![],
226 suppress_final_output: false,
227 };
228
229 let rendered = output.render();
230 assert!(rendered.contains("Extra info"));
231 }
232
233 #[test]
234 fn test_command_output_serialization() {
235 let output = CommandOutput {
236 data: json!({"test": true}),
237 headers: vec!["H".into()],
238 rows: vec![vec!["v".into()]],
239 format: OutputFormat::Json,
240 addendum: Some("info".into()),
241 warnings: vec![],
242 suppress_final_output: false,
243 };
244
245 let serialized = serde_json::to_string(&output).unwrap();
246 let deserialized: serde_json::Value = serde_json::from_str(&serialized).unwrap();
247
248 assert_eq!(deserialized["data"]["test"], true);
249 assert_eq!(deserialized["headers"][0], "H");
250 assert_eq!(deserialized["rows"][0][0], "v");
251 }
253
254 #[test]
255 fn test_command_output_render_empty_table_with_data() {
256 let output = CommandOutput {
257 data: json!({"key": "value"}),
258 headers: vec![],
259 rows: vec![],
260 format: OutputFormat::Table,
261 addendum: None,
262 warnings: vec![],
263 suppress_final_output: false,
264 };
265
266 let rendered = output.render();
267 assert!(rendered.contains("key") || rendered.contains("value") || rendered.is_empty());
269 }
270}