1use colored::{Color, Colorize};
30use serde::Serialize;
31use serde_json::ser::{Formatter, PrettyFormatter};
32use serde_json::Value;
33use std::io::{self, Write};
34
35#[derive(Debug, Clone)]
41pub struct FormatOptions {
42 pub indent: usize,
44 pub color: bool,
46 pub sort_keys: bool,
48 pub theme: Theme,
50}
51
52impl Default for FormatOptions {
53 fn default() -> Self {
54 Self {
55 indent: 2,
56 color: true,
57 sort_keys: false,
58 theme: Theme::default(),
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct Theme {
66 pub key: Color,
67 pub string: Color,
68 pub number: Color,
69 pub boolean: Color,
70 pub null: Color,
71}
72
73impl Default for Theme {
74 fn default() -> Self {
75 Self {
76 key: Color::Cyan,
77 string: Color::Green,
78 number: Color::Yellow,
79 boolean: Color::Magenta,
80 null: Color::BrightBlack, }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum QueryError {
88 KeyNotFound(String),
90 IndexOutOfBounds(usize),
92 InvalidQuery(String),
94}
95
96impl std::fmt::Display for QueryError {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 match self {
99 QueryError::KeyNotFound(key) => write!(f, "key '{}' not found", key),
100 QueryError::IndexOutOfBounds(idx) => write!(f, "index [{}] out of bounds", idx),
101 QueryError::InvalidQuery(msg) => write!(f, "invalid query: {}", msg),
102 }
103 }
104}
105
106impl std::error::Error for QueryError {}
107
108pub fn format_json(value: &Value, opts: &FormatOptions) -> String {
113 let mut writer = Vec::new();
114 let indent_buf = vec![b' '; opts.indent];
115
116 if opts.color {
117 let formatter = ColorFormatter::new(&indent_buf, &opts.theme);
118 let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
119 value.serialize(&mut ser).unwrap();
120 } else {
121 let formatter = PrettyFormatter::with_indent(&indent_buf);
122 let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
123 value.serialize(&mut ser).unwrap();
124 }
125
126 String::from_utf8(writer).unwrap_or_default()
127}
128
129pub fn format_json_compact(value: &Value) -> String {
139 serde_json::to_string(value).unwrap_or_default()
140}
141
142pub fn query<'a>(root: &'a Value, path: &str) -> Result<Vec<&'a Value>, QueryError> {
171 let segments = parse_query(path)?;
172 let mut current_matches = vec![root];
173
174 for seg in &segments {
175 let mut next_matches = Vec::new();
176 for val in current_matches {
177 match seg {
178 Segment::Key(key) => {
179 if let Some(v) = val.get(key) {
180 next_matches.push(v);
181 }
182 }
183 Segment::Index(idx) => {
184 if let Some(v) = val.get(*idx) {
185 next_matches.push(v);
186 }
187 }
188 Segment::Wildcard => {
189 if let Some(obj) = val.as_object() {
190 for v in obj.values() {
191 next_matches.push(v);
192 }
193 } else if let Some(arr) = val.as_array() {
194 for v in arr {
195 next_matches.push(v);
196 }
197 }
198 }
199 Segment::Slice(start, end) => {
200 if let Some(arr) = val.as_array() {
201 let start = start.unwrap_or(0);
202 let end = end.unwrap_or(arr.len()).min(arr.len());
203 if start < end {
204 for v in &arr[start..end] {
205 next_matches.push(v);
206 }
207 }
208 }
209 }
210 }
211 }
212 if next_matches.is_empty() {
213 match seg {
220 Segment::Key(key) if !path.is_empty() => return Err(QueryError::KeyNotFound(key.clone())),
221 Segment::Index(idx) => return Err(QueryError::IndexOutOfBounds(*idx)),
222 _ => {} }
224 }
225 current_matches = next_matches;
226 }
227
228 Ok(current_matches)
229}
230
231pub fn parse_and_format(json_str: &str, opts: &FormatOptions) -> Result<String, serde_json::Error> {
236 let value: Value = serde_json::from_str(json_str)?;
237 Ok(format_json(&value, opts))
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
245enum Segment {
246 Key(String),
247 Index(usize),
248 Wildcard, Slice(Option<usize>, Option<usize>), }
251
252fn parse_query(query: &str) -> Result<Vec<Segment>, QueryError> {
253 let mut segments = Vec::new();
254 let q = query.strip_prefix('.').unwrap_or(query);
255
256 if q.is_empty() {
257 return Ok(segments);
258 }
259
260 let mut chars = q.chars().peekable();
261 let mut buf = String::new();
262
263 while let Some(&ch) = chars.peek() {
264 match ch {
265 '.' => {
266 if !buf.is_empty() {
267 segments.push(Segment::Key(buf.clone()));
268 buf.clear();
269 }
270 chars.next();
271 if let Some(&'*') = chars.peek() {
272 segments.push(Segment::Wildcard);
273 chars.next();
274 }
275 }
276 '*' => {
277 segments.push(Segment::Wildcard);
278 chars.next();
279 }
280 '[' => {
281 if !buf.is_empty() {
282 segments.push(Segment::Key(buf.clone()));
283 buf.clear();
284 }
285 chars.next(); let mut idx_buf = String::new();
288 let mut found_bracket = false;
289 while let Some(&c) = chars.peek() {
290 if c == ']' {
291 chars.next();
292 found_bracket = true;
293 break;
294 }
295 idx_buf.push(c);
296 chars.next();
297 }
298
299 if !found_bracket {
300 return Err(QueryError::InvalidQuery("unclosed bracket".to_string()));
301 }
302
303 if idx_buf.is_empty() {
304 segments.push(Segment::Wildcard);
305 } else if idx_buf.contains(':') {
306 let parts: Vec<&str> = idx_buf.split(':').collect();
308 let start = if parts[0].is_empty() {
309 None
310 } else {
311 Some(parts[0].parse().map_err(|_| {
312 QueryError::InvalidQuery(format!("invalid slice start: {}", parts[0]))
313 })?)
314 };
315 let end = if parts.len() < 2 || parts[1].is_empty() {
316 None
317 } else {
318 Some(parts[1].parse().map_err(|_| {
319 QueryError::InvalidQuery(format!("invalid slice end: {}", parts[1]))
320 })?)
321 };
322 segments.push(Segment::Slice(start, end));
323 } else if let Ok(idx) = idx_buf.parse::<usize>() {
324 segments.push(Segment::Index(idx));
325 } else {
326 let key = idx_buf.trim_matches('"').trim_matches('\'').to_string();
328 segments.push(Segment::Key(key));
329 }
330 }
331 '"' => {
332 chars.next(); let mut key_buf = String::new();
335 let mut found_quote = false;
336 while let Some(&c) = chars.peek() {
337 if c == '"' {
338 chars.next();
339 found_quote = true;
340 break;
341 }
342 key_buf.push(c);
343 chars.next();
344 }
345 if !found_quote {
346 return Err(QueryError::InvalidQuery("unclosed quote".to_string()));
347 }
348 segments.push(Segment::Key(key_buf));
349 }
350 _ => {
351 buf.push(ch);
352 chars.next();
353 }
354 }
355 }
356
357 if !buf.is_empty() {
358 segments.push(Segment::Key(buf));
359 }
360
361 Ok(segments)
362}
363
364struct ColorFormatter<'a> {
369 pretty: PrettyFormatter<'a>,
370 is_key: bool,
371 theme: &'a Theme,
372}
373
374impl<'a> ColorFormatter<'a> {
375 fn new(indent: &'a [u8], theme: &'a Theme) -> Self {
376 Self {
377 pretty: PrettyFormatter::with_indent(indent),
378 is_key: false,
379 theme,
380 }
381 }
382}
383
384impl<'a> Formatter for ColorFormatter<'a> {
385 #[inline]
386 fn begin_array<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
387 self.pretty.begin_array(writer)
388 }
389
390 #[inline]
391 fn end_array<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
392 self.pretty.end_array(writer)
393 }
394
395 #[inline]
396 fn begin_array_value<W: ?Sized + Write>(&mut self, writer: &mut W, first: bool) -> io::Result<()> {
397 self.pretty.begin_array_value(writer, first)
398 }
399
400 #[inline]
401 fn end_array_value<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
402 self.pretty.end_array_value(writer)
403 }
404
405 #[inline]
406 fn begin_object<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
407 self.pretty.begin_object(writer)
408 }
409
410 #[inline]
411 fn end_object<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
412 self.pretty.end_object(writer)
413 }
414
415 #[inline]
416 fn begin_object_key<W: ?Sized + Write>(&mut self, writer: &mut W, first: bool) -> io::Result<()> {
417 self.is_key = true;
418 self.pretty.begin_object_key(writer, first)
419 }
420
421 #[inline]
422 fn end_object_key<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
423 self.is_key = false;
424 self.pretty.end_object_key(writer)
425 }
426
427 #[inline]
428 fn begin_object_value<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
429 self.pretty.begin_object_value(writer)
430 }
431
432 #[inline]
433 fn end_object_value<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
434 self.pretty.end_object_value(writer)
435 }
436
437 #[inline]
438 fn write_null<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
439 writer.write_all("null".color(self.theme.null).to_string().as_bytes())
440 }
441
442 #[inline]
443 fn write_bool<W: ?Sized + Write>(&mut self, writer: &mut W, value: bool) -> io::Result<()> {
444 let s = if value { "true" } else { "false" };
445 writer.write_all(s.color(self.theme.boolean).bold().to_string().as_bytes())
446 }
447
448 #[inline]
449 fn write_i64<W: ?Sized + Write>(&mut self, writer: &mut W, value: i64) -> io::Result<()> {
450 writer.write_all(value.to_string().color(self.theme.number).to_string().as_bytes())
451 }
452
453 #[inline]
454 fn write_u64<W: ?Sized + Write>(&mut self, writer: &mut W, value: u64) -> io::Result<()> {
455 writer.write_all(value.to_string().color(self.theme.number).to_string().as_bytes())
456 }
457
458 #[inline]
459 fn write_f64<W: ?Sized + Write>(&mut self, writer: &mut W, value: f64) -> io::Result<()> {
460 writer.write_all(value.to_string().color(self.theme.number).to_string().as_bytes())
461 }
462
463 #[inline]
464 fn write_string_fragment<W: ?Sized + Write>(&mut self, writer: &mut W, fragment: &str) -> io::Result<()> {
465 if self.is_key {
466 writer.write_all(fragment.color(self.theme.key).bold().to_string().as_bytes())
467 } else {
468 writer.write_all(fragment.color(self.theme.string).to_string().as_bytes())
469 }
470 }
471
472 #[inline]
473 fn begin_string<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
474 if self.is_key {
475 writer.write_all("\"".color(self.theme.key).bold().to_string().as_bytes())
476 } else {
477 writer.write_all("\"".color(self.theme.string).to_string().as_bytes())
478 }
479 }
480
481 #[inline]
482 fn end_string<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
483 if self.is_key {
484 writer.write_all("\"".color(self.theme.key).bold().to_string().as_bytes())
485 } else {
486 writer.write_all("\"".color(self.theme.string).to_string().as_bytes())
487 }
488 }
489
490 #[inline]
491 fn write_raw_fragment<W: ?Sized + Write>(&mut self, writer: &mut W, fragment: &str) -> io::Result<()> {
492 writer.write_all(fragment.as_bytes())
493 }
494}
495
496#[cfg(test)]
501mod tests {
502 use super::*;
503 use serde_json::json;
504
505 #[test]
506 fn test_compact_output() {
507 let val = json!({"a": 1, "b": [2, 3]});
508 let out = format_json_compact(&val);
509 assert!(out.contains("\"a\":1"));
510 assert!(!out.contains('\n'));
511 }
512
513 #[test]
514 fn test_pretty_plain_output() {
515 let val = json!({"name": "Alice"});
516 let opts = FormatOptions { indent: 2, color: false, ..FormatOptions::default() };
517 let out = format_json(&val, &opts);
518 assert!(out.contains("\"name\""));
519 assert!(out.contains("\"Alice\""));
520 assert!(out.contains('\n'));
521 }
522
523 #[test]
524 fn test_query_simple_key() {
525 let val = json!({"greeting": "hello"});
526 let result = query(&val, ".greeting").unwrap();
527 assert_eq!(result, vec![&json!("hello")]);
528 }
529
530 #[test]
531 fn test_query_nested() {
532 let val = json!({"a": {"b": {"c": 42}}});
533 let result = query(&val, ".a.b.c").unwrap();
534 assert_eq!(result, vec![&json!(42)]);
535 }
536
537 #[test]
538 fn test_query_array_index() {
539 let val = json!({"items": [10, 20, 30]});
540 let result = query(&val, ".items[1]").unwrap();
541 assert_eq!(result, vec![&json!(20)]);
542 }
543
544 #[test]
545 fn test_query_mixed() {
546 let val = json!({"users": [{"name": "Alice"}, {"name": "Bob"}]});
547 let result = query(&val, ".users[1].name").unwrap();
548 assert_eq!(result, vec![&json!("Bob")]);
549 }
550
551 #[test]
552 fn test_query_root() {
553 let val = json!({"a": 1});
554 let result = query(&val, ".").unwrap();
555 assert_eq!(result, vec![&val]);
556 }
557
558 #[test]
559 fn test_query_wildcard_array() {
560 let val = json!([1, 2, 3]);
561 let result = query(&val, "[]").unwrap();
562 assert_eq!(result.len(), 3);
563 assert_eq!(result[0], &json!(1));
564 }
565
566 #[test]
567 fn test_query_wildcard_object() {
568 let val = json!({"a": 1, "b": 2});
569 let result = query(&val, ".*").unwrap();
570 assert_eq!(result.len(), 2);
571 }
572
573 #[test]
574 fn test_query_slice() {
575 let val = json!([0, 1, 2, 3, 4]);
576 let result = query(&val, "[1:4]").unwrap();
577 assert_eq!(result.len(), 3);
578 assert_eq!(result[0], &json!(1));
579 assert_eq!(result[2], &json!(3));
580 }
581
582 #[test]
583 fn test_query_quoted_key() {
584 let val = json!({"key with spaces": "val"});
585 let result = query(&val, ".\"key with spaces\"").unwrap();
586 assert_eq!(result, vec![&json!("val")]);
587
588 let result2 = query(&val, "[\"key with spaces\"]").unwrap();
589 assert_eq!(result2, vec![&json!("val")]);
590 }
591
592 #[test]
593 fn test_query_key_not_found() {
594 let val = json!({"a": 1});
595 let err = query(&val, ".b").unwrap_err();
596 assert!(matches!(err, QueryError::KeyNotFound(_)));
597 }
598
599 #[test]
600 fn test_query_index_out_of_bounds() {
601 let val = json!([1, 2]);
602 let err = query(&val, "[5]").unwrap_err();
603 assert!(matches!(err, QueryError::IndexOutOfBounds(5)));
604 }
605
606 #[test]
607 fn test_parse_and_format() {
608 let json_str = r#"{"key": "value"}"#;
609 let opts = FormatOptions { indent: 2, color: false, ..FormatOptions::default() };
610 let result = parse_and_format(json_str, &opts).unwrap();
611 assert!(result.contains("\"key\""));
612 }
613
614 #[test]
615 fn test_parse_and_format_invalid() {
616 let opts = FormatOptions::default();
617 assert!(parse_and_format("not json", &opts).is_err());
618 }
619
620 #[test]
621 fn test_empty_object_and_array() {
622 let opts = FormatOptions { indent: 2, color: false, ..FormatOptions::default() };
623 assert_eq!(format_json(&json!({}), &opts), "{}");
624 assert_eq!(format_json(&json!([]), &opts), "[]");
625 }
626
627 #[test]
628 fn test_escape_special_characters() {
629 let val = json!({"msg": "line1\nline2\ttab"});
630 let opts = FormatOptions { indent: 2, color: false, ..FormatOptions::default() };
631 let out = format_json(&val, &opts);
632 assert!(out.contains("\\n"));
633 assert!(out.contains("\\t"));
634 }
635}