1use colored::Colorize;
30use serde_json::Value;
31
32#[derive(Debug, Clone)]
38pub struct FormatOptions {
39 pub indent: usize,
41 pub color: bool,
43}
44
45impl Default for FormatOptions {
46 fn default() -> Self {
47 Self {
48 indent: 2,
49 color: true,
50 }
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub enum QueryError {
57 KeyNotFound(String),
59 IndexOutOfBounds(usize),
61 InvalidQuery(String),
63}
64
65impl std::fmt::Display for QueryError {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 QueryError::KeyNotFound(key) => write!(f, "key '{}' not found", key),
69 QueryError::IndexOutOfBounds(idx) => write!(f, "index [{}] out of bounds", idx),
70 QueryError::InvalidQuery(msg) => write!(f, "invalid query: {}", msg),
71 }
72 }
73}
74
75impl std::error::Error for QueryError {}
76
77pub fn format_json(value: &Value, opts: &FormatOptions) -> String {
96 let mut buf = String::new();
97 if opts.color {
98 write_colored(&mut buf, value, 0, false, opts.indent);
99 } else {
100 write_plain(&mut buf, value, 0, false, opts.indent);
101 }
102 buf
103}
104
105pub fn format_json_compact(value: &Value) -> String {
115 serde_json::to_string(value).unwrap_or_default()
116}
117
118pub fn query<'a>(root: &'a Value, path: &str) -> Result<&'a Value, QueryError> {
134 let segments = parse_query(path)?;
135 let mut current = root;
136
137 for seg in &segments {
138 match seg {
139 Segment::Key(key) => {
140 current = current
141 .get(key.as_str())
142 .ok_or_else(|| QueryError::KeyNotFound(key.clone()))?;
143 }
144 Segment::Index(idx) => {
145 current = current
146 .get(*idx)
147 .ok_or(QueryError::IndexOutOfBounds(*idx))?;
148 }
149 }
150 }
151 Ok(current)
152}
153
154pub fn parse_and_format(json_str: &str, opts: &FormatOptions) -> Result<String, serde_json::Error> {
159 let value: Value = serde_json::from_str(json_str)?;
160 Ok(format_json(&value, opts))
161}
162
163#[derive(Debug)]
168enum Segment {
169 Key(String),
170 Index(usize),
171}
172
173fn parse_query(query: &str) -> Result<Vec<Segment>, QueryError> {
174 let mut segments = Vec::new();
175 let q = query.strip_prefix('.').unwrap_or(query);
176
177 if q.is_empty() {
178 return Ok(segments); }
180
181 let mut chars = q.chars().peekable();
182 let mut buf = String::new();
183
184 while let Some(&ch) = chars.peek() {
185 match ch {
186 '.' => {
187 if !buf.is_empty() {
188 segments.push(Segment::Key(buf.clone()));
189 buf.clear();
190 }
191 chars.next();
192 }
193 '[' => {
194 if !buf.is_empty() {
195 segments.push(Segment::Key(buf.clone()));
196 buf.clear();
197 }
198 chars.next(); let mut idx_buf = String::new();
200 let mut found_bracket = false;
201 while let Some(&c) = chars.peek() {
202 if c == ']' {
203 chars.next();
204 found_bracket = true;
205 break;
206 }
207 idx_buf.push(c);
208 chars.next();
209 }
210 if !found_bracket {
211 return Err(QueryError::InvalidQuery(
212 "unclosed bracket".to_string(),
213 ));
214 }
215 if let Ok(idx) = idx_buf.parse::<usize>() {
216 segments.push(Segment::Index(idx));
217 } else {
218 let key = idx_buf.trim_matches('"').trim_matches('\'').to_string();
220 segments.push(Segment::Key(key));
221 }
222 }
223 _ => {
224 buf.push(ch);
225 chars.next();
226 }
227 }
228 }
229 if !buf.is_empty() {
230 segments.push(Segment::Key(buf));
231 }
232
233 Ok(segments)
234}
235
236fn pad(buf: &mut String, indent_size: usize, level: usize) {
241 for _ in 0..(indent_size * level) {
242 buf.push(' ');
243 }
244}
245
246fn write_colored(buf: &mut String, value: &Value, level: usize, trailing_comma: bool, indent_size: usize) {
247 let comma = if trailing_comma { "," } else { "" };
248
249 match value {
250 Value::Null => {
251 buf.push_str(&format!("{}{}", "null".red().dimmed(), comma));
252 }
253 Value::Bool(b) => {
254 buf.push_str(&format!("{}{}", b.to_string().magenta().bold(), comma));
255 }
256 Value::Number(n) => {
257 buf.push_str(&format!("{}{}", n.to_string().yellow(), comma));
258 }
259 Value::String(s) => {
260 buf.push_str(&format!(
261 "{}{}",
262 format!("\"{}\"", escape_json_string(s)).green(),
263 comma
264 ));
265 }
266 Value::Array(arr) => {
267 if arr.is_empty() {
268 buf.push_str(&format!("[]{}", comma));
269 return;
270 }
271 buf.push_str("[\n");
272 for (i, item) in arr.iter().enumerate() {
273 pad(buf, indent_size, level + 1);
274 let has_comma = i < arr.len() - 1;
275 write_colored(buf, item, level + 1, has_comma, indent_size);
276 buf.push('\n');
277 }
278 pad(buf, indent_size, level);
279 buf.push_str(&format!("]{}", comma));
280 }
281 Value::Object(map) => {
282 if map.is_empty() {
283 buf.push_str(&format!("{{}}{}", comma));
284 return;
285 }
286 buf.push_str("{\n");
287 let len = map.len();
288 for (i, (key, val)) in map.iter().enumerate() {
289 let has_comma = i < len - 1;
290 pad(buf, indent_size, level + 1);
291 buf.push_str(&format!("{}: ", format!("\"{}\"", key).cyan().bold()));
292 write_colored(buf, val, level + 1, has_comma, indent_size);
293 buf.push('\n');
294 }
295 pad(buf, indent_size, level);
296 buf.push_str(&format!("}}{}", comma));
297 }
298 }
299}
300
301fn write_plain(buf: &mut String, value: &Value, level: usize, trailing_comma: bool, indent_size: usize) {
306 let comma = if trailing_comma { "," } else { "" };
307
308 match value {
309 Value::Null => {
310 buf.push_str(&format!("null{}", comma));
311 }
312 Value::Bool(b) => {
313 buf.push_str(&format!("{}{}", b, comma));
314 }
315 Value::Number(n) => {
316 buf.push_str(&format!("{}{}", n, comma));
317 }
318 Value::String(s) => {
319 buf.push_str(&format!("\"{}\"{}", escape_json_string(s), comma));
320 }
321 Value::Array(arr) => {
322 if arr.is_empty() {
323 buf.push_str(&format!("[]{}", comma));
324 return;
325 }
326 buf.push_str("[\n");
327 for (i, item) in arr.iter().enumerate() {
328 pad(buf, indent_size, level + 1);
329 let has_comma = i < arr.len() - 1;
330 write_plain(buf, item, level + 1, has_comma, indent_size);
331 buf.push('\n');
332 }
333 pad(buf, indent_size, level);
334 buf.push_str(&format!("]{}", comma));
335 }
336 Value::Object(map) => {
337 if map.is_empty() {
338 buf.push_str(&format!("{{}}{}", comma));
339 return;
340 }
341 buf.push_str("{\n");
342 let len = map.len();
343 for (i, (key, val)) in map.iter().enumerate() {
344 let has_comma = i < len - 1;
345 pad(buf, indent_size, level + 1);
346 buf.push_str(&format!("\"{}\": ", key));
347 write_plain(buf, val, level + 1, has_comma, indent_size);
348 buf.push('\n');
349 }
350 pad(buf, indent_size, level);
351 buf.push_str(&format!("}}{}", comma));
352 }
353 }
354}
355
356fn escape_json_string(s: &str) -> String {
361 let mut out = String::with_capacity(s.len());
362 for ch in s.chars() {
363 match ch {
364 '"' => out.push_str("\\\""),
365 '\\' => out.push_str("\\\\"),
366 '\n' => out.push_str("\\n"),
367 '\r' => out.push_str("\\r"),
368 '\t' => out.push_str("\\t"),
369 c if c.is_control() => {
370 out.push_str(&format!("\\u{:04x}", c as u32));
371 }
372 c => out.push(c),
373 }
374 }
375 out
376}
377
378#[cfg(test)]
383mod tests {
384 use super::*;
385 use serde_json::json;
386
387 #[test]
388 fn test_compact_output() {
389 let val = json!({"a": 1, "b": [2, 3]});
390 let out = format_json_compact(&val);
391 assert!(out.contains("\"a\":1"));
392 assert!(!out.contains('\n'));
393 }
394
395 #[test]
396 fn test_pretty_plain_output() {
397 let val = json!({"name": "Alice"});
398 let opts = FormatOptions { indent: 2, color: false };
399 let out = format_json(&val, &opts);
400 assert!(out.contains("\"name\""));
401 assert!(out.contains("\"Alice\""));
402 assert!(out.contains('\n'));
403 }
404
405 #[test]
406 fn test_query_simple_key() {
407 let val = json!({"greeting": "hello"});
408 let result = query(&val, ".greeting").unwrap();
409 assert_eq!(result, &json!("hello"));
410 }
411
412 #[test]
413 fn test_query_nested() {
414 let val = json!({"a": {"b": {"c": 42}}});
415 let result = query(&val, ".a.b.c").unwrap();
416 assert_eq!(result, &json!(42));
417 }
418
419 #[test]
420 fn test_query_array_index() {
421 let val = json!({"items": [10, 20, 30]});
422 let result = query(&val, ".items[1]").unwrap();
423 assert_eq!(result, &json!(20));
424 }
425
426 #[test]
427 fn test_query_mixed() {
428 let val = json!({"users": [{"name": "Alice"}, {"name": "Bob"}]});
429 let result = query(&val, ".users[1].name").unwrap();
430 assert_eq!(result, &json!("Bob"));
431 }
432
433 #[test]
434 fn test_query_root() {
435 let val = json!({"a": 1});
436 let result = query(&val, ".").unwrap();
437 assert_eq!(result, &val);
438 }
439
440 #[test]
441 fn test_query_key_not_found() {
442 let val = json!({"a": 1});
443 let err = query(&val, ".b").unwrap_err();
444 assert!(matches!(err, QueryError::KeyNotFound(_)));
445 }
446
447 #[test]
448 fn test_query_index_out_of_bounds() {
449 let val = json!([1, 2]);
450 let err = query(&val, "[5]").unwrap_err();
451 assert!(matches!(err, QueryError::IndexOutOfBounds(5)));
452 }
453
454 #[test]
455 fn test_parse_and_format() {
456 let json_str = r#"{"key": "value"}"#;
457 let opts = FormatOptions { indent: 2, color: false };
458 let result = parse_and_format(json_str, &opts).unwrap();
459 assert!(result.contains("\"key\""));
460 }
461
462 #[test]
463 fn test_parse_and_format_invalid() {
464 let opts = FormatOptions::default();
465 assert!(parse_and_format("not json", &opts).is_err());
466 }
467
468 #[test]
469 fn test_empty_object_and_array() {
470 let opts = FormatOptions { indent: 2, color: false };
471 assert_eq!(format_json(&json!({}), &opts), "{}");
472 assert_eq!(format_json(&json!([]), &opts), "[]");
473 }
474
475 #[test]
476 fn test_escape_special_characters() {
477 let val = json!({"msg": "line1\nline2\ttab"});
478 let opts = FormatOptions { indent: 2, color: false };
479 let out = format_json(&val, &opts);
480 assert!(out.contains("\\n"));
481 assert!(out.contains("\\t"));
482 }
483}