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