shard_den_json_extractor/
format.rs1use serde_json::Value;
4use shard_den_core::Result;
5
6#[derive(Debug, Clone, Copy, Default)]
8pub enum OutputFormat {
9 #[default]
10 Json,
11 Csv,
12 Text,
13 Yaml,
14}
15
16#[derive(Debug, Default)]
18pub struct Formatter;
19
20impl Formatter {
21 pub fn new() -> Self {
23 Self
24 }
25
26 pub fn format(&self, value: &Value, format: OutputFormat) -> Result<String> {
28 match format {
29 OutputFormat::Json => {
30 serde_json::to_string_pretty(value).map_err(shard_den_core::ShardDenError::Json)
31 }
32 OutputFormat::Csv => self.format_csv(value),
33 OutputFormat::Text => self.format_text(value),
34 OutputFormat::Yaml => self.format_yaml(value),
35 }
36 }
37
38 fn format_csv(&self, value: &Value) -> Result<String> {
39 match value {
41 Value::Array(arr) if !arr.is_empty() && arr.iter().all(|v| v.is_object()) => {
43 let mut headers: Vec<String> = Vec::new();
44 for item in arr {
45 if let Value::Object(obj) = item {
46 for key in obj.keys() {
47 if !headers.contains(key) {
48 headers.push(key.clone());
49 }
50 }
51 }
52 }
53
54 if headers.is_empty() {
55 return Ok(String::new());
56 }
57
58 let mut csv = headers.join(",") + "\n";
60 for item in arr {
61 if let Value::Object(obj) = item {
62 let row: Vec<String> = headers
63 .iter()
64 .map(|h| {
65 obj.get(h)
66 .map(|v| self.escape_csv_value(v))
67 .unwrap_or_default()
68 })
69 .collect();
70 csv.push_str(&row.join(","));
71 csv.push('\n');
72 }
73 }
74 Ok(csv)
75 }
76 Value::Array(arr) => {
78 let values: Vec<String> = arr.iter().map(|v| self.escape_csv_value(v)).collect();
79 Ok(values.join(","))
80 }
81 _ => Ok(self.escape_csv_value(value)),
83 }
84 }
85
86 fn escape_csv_value(&self, value: &Value) -> String {
87 match value {
88 Value::String(s) => {
89 if s.contains(',') || s.contains('"') || s.contains('\n') {
90 format!("\"{}\"", s.replace('"', "\"\""))
91 } else {
92 s.clone()
93 }
94 }
95 Value::Null => "".to_string(),
96 _ => value.to_string(),
97 }
98 }
99
100 fn format_text(&self, value: &Value) -> Result<String> {
101 match value {
102 Value::String(s) => Ok(s.clone()),
103 Value::Array(arr) => {
104 let items: Vec<String> = arr.iter().map(|v| self.value_to_text(v)).collect();
105 Ok(items.join("\n"))
106 }
107 _ => Ok(self.value_to_text(value)),
108 }
109 }
110
111 fn value_to_text(&self, value: &Value) -> String {
112 match value {
113 Value::String(s) => s.clone(),
114 Value::Null => "null".to_string(),
115 Value::Bool(b) => b.to_string(),
116 Value::Number(n) => n.to_string(),
117 Value::Array(arr) => {
118 let items: Vec<String> = arr.iter().map(|v| self.value_to_text(v)).collect();
119 format!("[{}]", items.join(", "))
120 }
121 Value::Object(obj) => {
122 let pairs: Vec<String> = obj
123 .iter()
124 .map(|(k, v)| format!("{}: {}", k, self.value_to_text(v)))
125 .collect();
126 format!("{{{}}}", pairs.join(", "))
127 }
128 }
129 }
130
131 fn format_yaml(&self, value: &Value) -> Result<String> {
132 serde_yaml::to_string(value).map_err(shard_den_core::ShardDenError::Yaml)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use serde_json::json;
140
141 #[test]
142 fn test_format_json() {
143 let formatter = Formatter::new();
144 let value = json!({"key": "value"});
145 let result = formatter.format(&value, OutputFormat::Json);
146 assert!(result.is_ok());
147 }
148
149 #[test]
150 fn test_format_json_pretty() {
151 let formatter = Formatter::new();
152 let value = json!({"a": 1, "b": 2});
153 let result = formatter.format(&value, OutputFormat::Json).unwrap();
154 assert!(result.contains("a"));
155 assert!(result.contains("1"));
156 }
157
158 #[test]
159 fn test_format_text_string() {
160 let formatter = Formatter::new();
161 let value = json!("hello world");
162 let result = formatter.format(&value, OutputFormat::Text).unwrap();
163 assert_eq!(result, "hello world");
164 }
165
166 #[test]
167 fn test_format_text_number() {
168 let formatter = Formatter::new();
169 let value = json!(42);
170 let result = formatter.format(&value, OutputFormat::Text).unwrap();
171 assert_eq!(result, "42");
172 }
173
174 #[test]
175 fn test_format_text_boolean() {
176 let formatter = Formatter::new();
177 let value = json!(true);
178 let result = formatter.format(&value, OutputFormat::Text).unwrap();
179 assert_eq!(result, "true");
180 }
181
182 #[test]
183 fn test_format_text_null() {
184 let formatter = Formatter::new();
185 let value = json!(null);
186 let result = formatter.format(&value, OutputFormat::Text).unwrap();
187 assert_eq!(result, "null");
188 }
189
190 #[test]
191 fn test_format_text_array() {
192 let formatter = Formatter::new();
193 let value = json!([1, 2, 3]);
194 let result = formatter.format(&value, OutputFormat::Text).unwrap();
195 assert!(result.contains("1"));
196 }
197
198 #[test]
199 fn test_format_text_object() {
200 let formatter = Formatter::new();
201 let value = json!({"a": 1});
202 let result = formatter.format(&value, OutputFormat::Text).unwrap();
203 assert!(result.contains("a"));
204 }
205
206 #[test]
207 fn test_format_csv_array_of_objects() {
208 let formatter = Formatter::new();
209 let value = json!([
210 {"id": 1, "name": "alice"},
211 {"id": 2, "name": "bob"}
212 ]);
213 let result = formatter.format(&value, OutputFormat::Csv).unwrap();
214 let expected = "id,name\n1,alice\n2,bob\n";
215 assert_eq!(result, expected);
216 }
217
218 #[test]
219 fn test_format_csv_simple_array() {
220 let formatter = Formatter::new();
221 let value = json!([1, 2, 3]);
222 let result = formatter.format(&value, OutputFormat::Csv).unwrap();
223 assert_eq!(result, "1,2,3");
224 }
225
226 #[test]
227 fn test_format_csv_single_value() {
228 let formatter = Formatter::new();
229 let value = json!("hello");
230 let result = formatter.format(&value, OutputFormat::Csv).unwrap();
231 assert_eq!(result, "hello");
232 }
233
234 #[test]
235 fn test_format_csv_escape_quotes() {
236 let formatter = Formatter::new();
237 let value = json!(["hello,world", "test\"value"]);
238 let result = formatter.format(&value, OutputFormat::Csv).unwrap();
239 assert!(result.contains("\""));
240 }
241
242 #[test]
243 fn test_format_yaml() {
244 let formatter = Formatter::new();
245 let value = json!({"key": "value"});
246 let result = formatter.format(&value, OutputFormat::Yaml);
247 assert!(result.is_ok());
248 }
249
250 #[test]
251 fn test_format_yaml_array() {
252 let formatter = Formatter::new();
253 let value = json!([1, 2, 3]);
254 let result = formatter.format(&value, OutputFormat::Yaml).unwrap();
255 assert!(result.contains("- 1"));
256 }
257
258 #[test]
259 fn test_format_csv_with_null() {
260 let formatter = Formatter::new();
261 let value = json!([{"name": null}, {"name": "test"}]);
263 let result = formatter.format(&value, OutputFormat::Csv);
264 assert!(result.is_ok());
266 }
267
268 #[test]
269 fn test_format_csv_empty_array() {
270 let formatter = Formatter::new();
271 let value = json!([]);
273 let result = formatter.format(&value, OutputFormat::Csv);
274 assert!(result.is_ok());
275 }
276
277 #[test]
278 fn test_value_to_text_string() {
279 let formatter = Formatter::new();
280 let value = json!("hello");
282 let result = formatter.format(&value, OutputFormat::Text);
283 assert!(result.is_ok());
284 assert_eq!(result.unwrap(), "hello");
285 }
286
287 #[test]
288 fn test_value_to_text_nested_array() {
289 let formatter = Formatter::new();
290 let value = json!([[1, 2], [3, 4]]);
292 let result = formatter.format(&value, OutputFormat::Text);
293 assert!(result.is_ok());
294 }
295
296 #[test]
297 fn test_format_csv_empty_headers() {
298 let formatter = Formatter::new();
299 let value = json!([{}, {}]);
301 let result = formatter.format(&value, OutputFormat::Csv).unwrap();
302 assert_eq!(result, "");
304 }
305
306 #[test]
307 fn test_value_to_text_direct_string() {
308 let formatter = Formatter::new();
309 let value = json!("direct_string");
311 let result = formatter.format(&value, OutputFormat::Text).unwrap();
312 assert_eq!(result, "direct_string");
313 }
314}