alopex_cli/output/
json.rs1use std::io::Write;
6
7use crate::error::Result;
8use crate::models::{Column, Row, Value};
9
10use super::formatter::Formatter;
11
12pub struct JsonFormatter {
17 columns: Vec<String>,
19 first_row: bool,
21}
22
23impl JsonFormatter {
24 pub fn new() -> Self {
26 Self {
27 columns: Vec::new(),
28 first_row: true,
29 }
30 }
31}
32
33impl Default for JsonFormatter {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl Formatter for JsonFormatter {
40 fn write_header(&mut self, writer: &mut dyn Write, columns: &[Column]) -> Result<()> {
41 self.columns = columns.iter().map(|c| c.name.clone()).collect();
43 self.first_row = true;
44 writeln!(writer, "[")?;
45 Ok(())
46 }
47
48 fn write_row(&mut self, writer: &mut dyn Write, row: &Row) -> Result<()> {
49 let mut obj = serde_json::Map::new();
51
52 for (i, value) in row.columns.iter().enumerate() {
53 let key = self
54 .columns
55 .get(i)
56 .cloned()
57 .unwrap_or_else(|| format!("col{}", i));
58 let json_value = value_to_json(value);
59 obj.insert(key, json_value);
60 }
61
62 let json = serde_json::Value::Object(obj);
63 let json_str = serde_json::to_string_pretty(&json)?;
64
65 if self.first_row {
67 self.first_row = false;
68 } else {
69 write!(writer, ",")?;
70 writeln!(writer)?;
71 }
72
73 for line in json_str.lines() {
75 writeln!(writer, " {}", line)?;
76 }
77
78 Ok(())
79 }
80
81 fn write_footer(&mut self, writer: &mut dyn Write) -> Result<()> {
82 if !self.first_row {
84 writeln!(writer)?;
85 }
86 writeln!(writer, "]")?;
87 Ok(())
88 }
89
90 fn supports_streaming(&self) -> bool {
91 true
92 }
93}
94
95fn value_to_json(value: &Value) -> serde_json::Value {
97 match value {
98 Value::Null => serde_json::Value::Null,
99 Value::Bool(b) => serde_json::Value::Bool(*b),
100 Value::Int(i) => serde_json::Value::Number((*i).into()),
101 Value::Float(f) => serde_json::Number::from_f64(*f)
102 .map(serde_json::Value::Number)
103 .unwrap_or(serde_json::Value::Null),
104 Value::Text(s) => serde_json::Value::String(s.clone()),
105 Value::Bytes(b) => {
106 serde_json::Value::Array(
108 b.iter()
109 .map(|&byte| serde_json::Value::Number(byte.into()))
110 .collect(),
111 )
112 }
113 Value::Vector(v) => serde_json::Value::Array(
114 v.iter()
115 .filter_map(|&f| serde_json::Number::from_f64(f as f64))
116 .map(serde_json::Value::Number)
117 .collect(),
118 ),
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::models::DataType;
126
127 fn test_columns() -> Vec<Column> {
128 vec![
129 Column::new("id", DataType::Int),
130 Column::new("name", DataType::Text),
131 ]
132 }
133
134 #[test]
135 fn test_json_empty() {
136 let mut formatter = JsonFormatter::new();
137 let mut output = Vec::new();
138
139 let columns = test_columns();
140 formatter.write_header(&mut output, &columns).unwrap();
141 formatter.write_footer(&mut output).unwrap();
142
143 let result = String::from_utf8(output).unwrap();
144 assert!(result.contains('['));
145 assert!(result.contains(']'));
146 }
147
148 #[test]
149 fn test_json_single_row() {
150 let mut formatter = JsonFormatter::new();
151 let mut output = Vec::new();
152
153 let columns = test_columns();
154 formatter.write_header(&mut output, &columns).unwrap();
155
156 let row = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
157 formatter.write_row(&mut output, &row).unwrap();
158
159 formatter.write_footer(&mut output).unwrap();
160
161 let result = String::from_utf8(output).unwrap();
162 assert!(result.contains("\"id\": 1"));
163 assert!(result.contains("\"name\": \"Alice\""));
164
165 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
167 assert!(parsed.is_array());
168 assert_eq!(parsed.as_array().unwrap().len(), 1);
169 }
170
171 #[test]
172 fn test_json_multiple_rows() {
173 let mut formatter = JsonFormatter::new();
174 let mut output = Vec::new();
175
176 let columns = test_columns();
177 formatter.write_header(&mut output, &columns).unwrap();
178
179 let row1 = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
180 let row2 = Row::new(vec![Value::Int(2), Value::Text("Bob".to_string())]);
181
182 formatter.write_row(&mut output, &row1).unwrap();
183 formatter.write_row(&mut output, &row2).unwrap();
184
185 formatter.write_footer(&mut output).unwrap();
186
187 let result = String::from_utf8(output).unwrap();
188
189 let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
191 assert!(parsed.is_array());
192 assert_eq!(parsed.as_array().unwrap().len(), 2);
193 }
194
195 #[test]
196 fn test_json_null_value() {
197 let mut formatter = JsonFormatter::new();
198 let mut output = Vec::new();
199
200 let columns = test_columns();
201 formatter.write_header(&mut output, &columns).unwrap();
202
203 let row = Row::new(vec![Value::Int(1), Value::Null]);
204 formatter.write_row(&mut output, &row).unwrap();
205
206 formatter.write_footer(&mut output).unwrap();
207
208 let result = String::from_utf8(output).unwrap();
209 assert!(result.contains("\"name\": null"));
210 }
211
212 #[test]
213 fn test_json_supports_streaming() {
214 let formatter = JsonFormatter::new();
215 assert!(formatter.supports_streaming());
216 }
217}