Skip to main content

indodax_cli/output/
mod.rs

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        // Test that OutputFormat works with clap ValueEnum
133        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        // format and addendum should be skipped (marked with #[serde(skip)])
256    }
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        // Should fall back to rendering data as JSON
272        assert!(rendered.contains("key") || rendered.contains("value") || rendered.is_empty());
273    }
274}