alopex_cli/output/
jsonl.rs1use std::io::Write;
7
8use crate::error::Result;
9use crate::models::{Column, Row, Value};
10
11use super::formatter::Formatter;
12
13pub struct JsonlFormatter {
18 columns: Vec<String>,
20}
21
22impl JsonlFormatter {
23 pub fn new() -> Self {
25 Self {
26 columns: Vec::new(),
27 }
28 }
29}
30
31impl Default for JsonlFormatter {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl Formatter for JsonlFormatter {
38 fn write_header(&mut self, _writer: &mut dyn Write, columns: &[Column]) -> Result<()> {
39 self.columns = columns.iter().map(|c| c.name.clone()).collect();
41 Ok(())
42 }
43
44 fn write_row(&mut self, writer: &mut dyn Write, row: &Row) -> Result<()> {
45 let mut obj = serde_json::Map::new();
47
48 for (i, value) in row.columns.iter().enumerate() {
49 let key = self
50 .columns
51 .get(i)
52 .cloned()
53 .unwrap_or_else(|| format!("col{}", i));
54 let json_value = value_to_json(value);
55 obj.insert(key, json_value);
56 }
57
58 let json = serde_json::Value::Object(obj);
59 writeln!(writer, "{}", json)?;
60 Ok(())
61 }
62
63 fn write_footer(&mut self, _writer: &mut dyn Write) -> Result<()> {
64 Ok(())
66 }
67
68 fn supports_streaming(&self) -> bool {
69 true
70 }
71}
72
73fn value_to_json(value: &Value) -> serde_json::Value {
75 match value {
76 Value::Null => serde_json::Value::Null,
77 Value::Bool(b) => serde_json::Value::Bool(*b),
78 Value::Int(i) => serde_json::Value::Number((*i).into()),
79 Value::Float(f) => serde_json::Number::from_f64(*f)
80 .map(serde_json::Value::Number)
81 .unwrap_or(serde_json::Value::Null),
82 Value::Text(s) => serde_json::Value::String(s.clone()),
83 Value::Bytes(b) => {
84 serde_json::Value::Array(
86 b.iter()
87 .map(|&byte| serde_json::Value::Number(byte.into()))
88 .collect(),
89 )
90 }
91 Value::Vector(v) => serde_json::Value::Array(
92 v.iter()
93 .filter_map(|&f| serde_json::Number::from_f64(f as f64))
94 .map(serde_json::Value::Number)
95 .collect(),
96 ),
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::models::DataType;
104
105 fn test_columns() -> Vec<Column> {
106 vec![
107 Column::new("id", DataType::Int),
108 Column::new("name", DataType::Text),
109 ]
110 }
111
112 #[test]
113 fn test_jsonl_basic() {
114 let mut formatter = JsonlFormatter::new();
115 let mut output = Vec::new();
116
117 let columns = test_columns();
118 formatter.write_header(&mut output, &columns).unwrap();
119
120 let row = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
121 formatter.write_row(&mut output, &row).unwrap();
122
123 formatter.write_footer(&mut output).unwrap();
124
125 let result = String::from_utf8(output).unwrap();
126 assert_eq!(result, r#"{"id":1,"name":"Alice"}"#.to_string() + "\n");
127 }
128
129 #[test]
130 fn test_jsonl_multiple_rows() {
131 let mut formatter = JsonlFormatter::new();
132 let mut output = Vec::new();
133
134 let columns = test_columns();
135 formatter.write_header(&mut output, &columns).unwrap();
136
137 let row1 = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
138 let row2 = Row::new(vec![Value::Int(2), Value::Text("Bob".to_string())]);
139
140 formatter.write_row(&mut output, &row1).unwrap();
141 formatter.write_row(&mut output, &row2).unwrap();
142
143 formatter.write_footer(&mut output).unwrap();
144
145 let result = String::from_utf8(output).unwrap();
146 let lines: Vec<&str> = result.lines().collect();
147 assert_eq!(lines.len(), 2);
148 assert!(lines[0].contains("\"id\":1"));
149 assert!(lines[0].contains("\"name\":\"Alice\""));
150 assert!(lines[1].contains("\"id\":2"));
151 assert!(lines[1].contains("\"name\":\"Bob\""));
152 }
153
154 #[test]
155 fn test_jsonl_null_value() {
156 let mut formatter = JsonlFormatter::new();
157 let mut output = Vec::new();
158
159 let columns = test_columns();
160 formatter.write_header(&mut output, &columns).unwrap();
161
162 let row = Row::new(vec![Value::Int(1), Value::Null]);
163 formatter.write_row(&mut output, &row).unwrap();
164
165 let result = String::from_utf8(output).unwrap();
166 assert!(result.contains("\"name\":null"));
167 }
168
169 #[test]
170 fn test_jsonl_supports_streaming() {
171 let formatter = JsonlFormatter::new();
172 assert!(formatter.supports_streaming());
173 }
174}