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}
29
30impl CommandOutput {
31 pub fn new(
32 data: serde_json::Value,
33 headers: Vec<String>,
34 rows: Vec<Vec<String>>,
35 ) -> Self {
36 Self {
37 data,
38 headers,
39 rows,
40 format: OutputFormat::Table,
41 addendum: None,
42 }
43 }
44
45 pub fn json(data: serde_json::Value) -> Self {
46 Self {
47 data,
48 headers: vec![],
49 rows: vec![],
50 format: OutputFormat::Table,
51 addendum: None,
52 }
53 }
54
55 pub fn new_empty() -> Self {
56 Self {
57 data: serde_json::json!({}),
58 headers: vec![],
59 rows: vec![],
60 format: OutputFormat::Table,
61 addendum: None,
62 }
63 }
64
65 pub fn with_format(mut self, format: OutputFormat) -> Self {
66 self.format = format;
67 self
68 }
69
70 pub fn with_addendum(mut self, addendum: impl Into<String>) -> Self {
71 self.addendum = Some(addendum.into());
72 self
73 }
74
75 pub fn render(&self) -> String {
76 match self.format {
77 OutputFormat::Table => table::render(self),
78 OutputFormat::Json => json::render(self),
79 }
80 }
81}
82
83pub mod table;
84pub mod json;
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use serde_json::json;
90
91 #[test]
92 fn test_output_format_display_table() {
93 assert_eq!(format!("{}", OutputFormat::Table), "table");
94 }
95
96 #[test]
97 fn test_output_format_display_json() {
98 assert_eq!(format!("{}", OutputFormat::Json), "json");
99 }
100
101 #[test]
102 fn test_output_format_from_clap() {
103 let formats = vec![OutputFormat::Table, OutputFormat::Json];
105 assert_eq!(formats.len(), 2);
106 }
107
108 #[test]
109 fn test_command_output_new() {
110 let data = json!({"key": "value"});
111 let headers = vec!["H1".into(), "H2".into()];
112 let rows = vec![vec!["a".into(), "b".into()]];
113 let output = CommandOutput::new(data.clone(), headers.clone(), rows.clone());
114
115 assert_eq!(output.data, data);
116 assert_eq!(output.headers, headers);
117 assert_eq!(output.rows, rows);
118 assert_eq!(output.format, OutputFormat::Table);
119 assert!(output.addendum.is_none());
120 }
121
122 #[test]
123 fn test_command_output_json() {
124 let data = json!({"test": 123});
125 let output = CommandOutput::json(data.clone());
126
127 assert_eq!(output.data, data);
128 assert!(output.headers.is_empty());
129 assert!(output.rows.is_empty());
130 assert_eq!(output.format, OutputFormat::Table);
131 }
132
133 #[test]
134 fn test_command_output_new_empty() {
135 let output = CommandOutput::new_empty();
136
137 assert_eq!(output.data, json!({}));
138 assert!(output.headers.is_empty());
139 assert!(output.rows.is_empty());
140 }
141
142 #[test]
143 fn test_command_output_with_format() {
144 let output = CommandOutput::json(json!({"test": true}))
145 .with_format(OutputFormat::Json);
146
147 assert_eq!(output.format, OutputFormat::Json);
148 }
149
150 #[test]
151 fn test_command_output_with_addendum() {
152 let output = CommandOutput::new_empty()
153 .with_addendum("test addendum");
154
155 assert_eq!(output.addendum, Some("test addendum".into()));
156 }
157
158 #[test]
159 fn test_command_output_render_table() {
160 let output = CommandOutput {
161 data: json!({"key": "value"}),
162 headers: vec!["Key".into(), "Value".into()],
163 rows: vec![vec!["key".into(), "value".into()]],
164 format: OutputFormat::Table,
165 addendum: None,
166 };
167
168 let rendered = output.render();
169 assert!(!rendered.is_empty());
170 }
171
172 #[test]
173 fn test_command_output_render_json() {
174 let data = json!({"hello": "world"});
175 let output = CommandOutput::json(data.clone()).with_format(OutputFormat::Json);
176
177 let rendered = output.render();
178 let parsed: serde_json::Value = serde_json::from_str(&rendered).unwrap();
179 assert_eq!(parsed["data"], data);
180 }
181
182 #[test]
183 fn test_command_output_render_json_empty() {
184 let output = CommandOutput::new_empty().with_format(OutputFormat::Json);
185 let rendered = output.render();
186 let parsed: serde_json::Value = serde_json::from_str(&rendered).unwrap();
187 assert_eq!(parsed["data"], json!({}));
188 }
189
190 #[test]
191 fn test_command_output_render_table_with_addendum() {
192 let output = CommandOutput {
193 data: json!({}),
194 headers: vec![],
195 rows: vec![],
196 format: OutputFormat::Table,
197 addendum: Some("Extra info".into()),
198 };
199
200 let rendered = output.render();
201 assert!(rendered.contains("Extra info"));
202 }
203
204 #[test]
205 fn test_command_output_serialization() {
206 let output = CommandOutput {
207 data: json!({"test": true}),
208 headers: vec!["H".into()],
209 rows: vec![vec!["v".into()]],
210 format: OutputFormat::Json,
211 addendum: Some("info".into()),
212 };
213
214 let serialized = serde_json::to_string(&output).unwrap();
215 let deserialized: serde_json::Value = serde_json::from_str(&serialized).unwrap();
216
217 assert_eq!(deserialized["data"]["test"], true);
218 assert_eq!(deserialized["headers"][0], "H");
219 assert_eq!(deserialized["rows"][0][0], "v");
220 }
222
223 #[test]
224 fn test_command_output_render_empty_table_with_data() {
225 let output = CommandOutput {
226 data: json!({"key": "value"}),
227 headers: vec![],
228 rows: vec![],
229 format: OutputFormat::Table,
230 addendum: None,
231 };
232
233 let rendered = output.render();
234 assert!(rendered.contains("key") || rendered.contains("value") || rendered.is_empty());
236 }
237}