1use std::collections::HashMap;
39
40use crate::console::{Console, ConsoleOptions};
41use crate::errors::{Result, RichError};
42use crate::protocol::Renderable;
43use crate::segment::Segment;
44use crate::style::Style;
45
46pub struct Json {
48 value: Node,
49 styles: JsonStyles,
50 no_wrap: bool,
52 #[cfg(feature = "json-escape-safe")]
53 escape_safe: bool,
54}
55
56#[derive(Debug)]
62enum Node {
63 Null,
64 Bool(bool),
65 Number(String),
66 NonFinite(&'static str),
70 Str(String),
71 Array(Vec<Node>),
72 Object(Vec<(String, Node)>),
73}
74
75impl Drop for Node {
76 fn drop(&mut self) {
83 let mut pending: Vec<Node> = Vec::new();
84 take_children(self, &mut pending);
85 while let Some(mut node) = pending.pop() {
86 take_children(&mut node, &mut pending);
87 }
90 }
91}
92
93fn take_children(node: &mut Node, out: &mut Vec<Node>) {
95 match node {
96 Node::Array(items) => out.append(items),
97 Node::Object(entries) => out.extend(entries.drain(..).map(|(_, value)| value)),
98 _ => {}
99 }
100}
101
102struct JsonStyles {
103 brace: Style,
104 key: Style,
105 string: Style,
106 number: Style,
107 bool_true: Style,
108 bool_false: Style,
109 null: Style,
110}
111
112impl JsonStyles {
113 fn defaults() -> Self {
114 let s = |spec: &str| Style::parse(spec).expect("valid built-in style");
115 JsonStyles {
116 brace: s("bold"),
117 key: s("bold blue"),
118 string: s("green"),
119 number: s("bold cyan"),
120 bool_true: s("italic bright_green"),
121 bool_false: s("italic bright_red"),
122 null: s("italic magenta"),
123 }
124 }
125}
126
127enum Task<'a> {
129 Value(&'a Node, usize),
131 Emit(Segment),
133}
134
135impl Json {
136 pub fn new(text: &str) -> Result<Self> {
138 Ok(Json {
139 value: Parser::new(text).parse_document()?,
140 styles: JsonStyles::defaults(),
141 no_wrap: false,
142 #[cfg(feature = "json-escape-safe")]
143 escape_safe: false,
144 })
145 }
146
147 #[cfg(feature = "json-escape-safe")]
152 pub fn escape_safe(mut self, enabled: bool) -> Self {
153 self.escape_safe = enabled;
154 self
155 }
156
157 #[must_use]
182 pub fn no_wrap(mut self, no_wrap: bool) -> Self {
183 self.no_wrap = no_wrap;
184 self
185 }
186
187 fn render_value(&self) -> Vec<Segment> {
192 let brace = |text: &str| Segment::new(text.to_string(), Some(self.styles.brace.clone()));
193 let plain = |text: String| Segment::new(text, None);
194
195 let mut out = Vec::new();
196 let mut stack = vec![Task::Value(&self.value, 0)];
197 while let Some(task) = stack.pop() {
198 let (node, level) = match task {
199 Task::Emit(segment) => {
200 out.push(segment);
201 continue;
202 }
203 Task::Value(node, level) => (node, level),
204 };
205 match node {
206 Node::Null => out.push(Segment::new(
207 "null".to_string(),
208 Some(self.styles.null.clone()),
209 )),
210 Node::Bool(true) => out.push(Segment::new(
211 "true".to_string(),
212 Some(self.styles.bool_true.clone()),
213 )),
214 Node::Bool(false) => out.push(Segment::new(
215 "false".to_string(),
216 Some(self.styles.bool_false.clone()),
217 )),
218 Node::Number(number) => out.push(Segment::new(
219 number.clone(),
220 Some(self.styles.number.clone()),
221 )),
222 Node::NonFinite(literal) => out.push(plain((*literal).to_string())),
223 Node::Str(string) => out.push(Segment::new(
224 quote(string),
225 Some(self.styles.string.clone()),
226 )),
227 Node::Array(items) => {
228 out.push(brace("["));
229 if items.is_empty() {
230 out.push(brace("]"));
231 continue;
232 }
233 out.push(plain("\n".to_string()));
234 stack.push(Task::Emit(brace("]")));
236 stack.push(Task::Emit(plain(" ".repeat(level))));
237 let last = items.len() - 1;
238 for (index, item) in items.iter().enumerate().rev() {
239 stack.push(Task::Emit(plain("\n".to_string())));
240 if index != last {
241 stack.push(Task::Emit(plain(",".to_string())));
242 }
243 stack.push(Task::Value(item, level + 1));
244 stack.push(Task::Emit(plain(" ".repeat(level + 1))));
245 }
246 }
247 Node::Object(entries) => {
248 out.push(brace("{"));
249 if entries.is_empty() {
250 out.push(brace("}"));
251 continue;
252 }
253 out.push(plain("\n".to_string()));
254 stack.push(Task::Emit(brace("}")));
255 stack.push(Task::Emit(plain(" ".repeat(level))));
256 let last = entries.len() - 1;
257 for (index, (key, item)) in entries.iter().enumerate().rev() {
258 stack.push(Task::Emit(plain("\n".to_string())));
259 if index != last {
260 stack.push(Task::Emit(plain(",".to_string())));
261 }
262 stack.push(Task::Value(item, level + 1));
263 stack.push(Task::Emit(plain(": ".to_string())));
264 stack.push(Task::Emit(Segment::new(
265 quote(key),
266 Some(self.styles.key.clone()),
267 )));
268 stack.push(Task::Emit(plain(" ".repeat(level + 1))));
269 }
270 }
271 }
272 }
273 out
274 }
275}
276
277impl Renderable for Json {
278 fn rich_render(&self, _console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
279 let segments = self.render_value();
280 #[cfg(feature = "json-escape-safe")]
281 if self.escape_safe {
282 return escape_safe_lines(&segments, options.max_width, self.no_wrap);
283 }
284 if self.no_wrap {
285 Segment::crop_lines(&segments, options.max_width)
289 } else {
290 Segment::fold_lines_words(&segments, options.max_width)
295 }
296 }
297}
298
299#[cfg(feature = "json-escape-safe")]
303fn escape_safe_lines(segments: &[Segment], width: usize, crop: bool) -> Vec<Segment> {
304 if width == 0 {
305 return Vec::new();
306 }
307 let lines = Segment::split_lines(segments);
308 let last = lines.len().saturating_sub(1);
309 let mut out = Vec::new();
310 for (line_index, line) in lines.into_iter().enumerate() {
311 let plain: String = line
312 .iter()
313 .filter(|s| !s.control)
314 .map(|s| s.text.as_str())
315 .collect();
316 if !plain.contains('\\') {
318 out.extend(if crop {
319 Segment::crop_lines(&line, width)
320 } else {
321 Segment::fold_lines_words(&line, width)
322 });
323 } else {
324 let (spans, _) = crate::cells::split_graphemes(&plain);
325 let mut atoms = Vec::new();
326 let mut index = 0;
327 while index < spans.len() {
328 let (start, mut end, mut cells) = spans[index];
329 if plain.as_bytes()[start] == b'\\' {
330 let escape_end = start
331 + if plain.as_bytes().get(start + 1) == Some(&b'u') {
332 6
333 } else {
334 2
335 };
336 while end < escape_end && index + 1 < spans.len() {
337 index += 1;
338 end = spans[index].1;
339 cells += spans[index].2;
340 }
341 if !crop && cells > width {
342 for offset in start..escape_end {
346 atoms.push((offset, offset + 1, 1));
347 }
348 if end > escape_end {
349 atoms.push((escape_end, end, 0));
350 }
351 index += 1;
352 continue;
353 }
354 }
355 atoms.push((start, end, cells));
356 index += 1;
357 }
358 let mut breaks = Vec::new();
359 let mut cells = 0;
360 let mut stop = plain.len();
361 for (start, _, atom_width) in atoms {
362 if cells + atom_width > width {
363 if crop {
364 stop = start;
365 break;
366 }
367 if cells > 0 {
368 breaks.push(start);
369 cells = 0;
370 }
371 }
372 cells += atom_width;
373 }
374 let mut position = 0;
375 let mut next = 0;
376 for segment in line {
377 if segment.control {
378 out.push(segment);
379 continue;
380 }
381 let mut buffer = String::new();
382 for ch in segment.text.chars() {
383 if position >= stop {
384 break;
385 }
386 if breaks.get(next) == Some(&position) {
387 if !buffer.is_empty() {
388 out.push(Segment::new(
389 std::mem::take(&mut buffer),
390 segment.style.clone(),
391 ));
392 }
393 out.push(Segment::line());
394 next += 1;
395 }
396 buffer.push(ch);
397 position += ch.len_utf8();
398 }
399 if !buffer.is_empty() {
400 out.push(Segment::new(buffer, segment.style));
401 }
402 }
403 }
404 if line_index != last {
405 out.push(Segment::line());
406 }
407 }
408 out
409}
410
411fn quote(string: &str) -> String {
413 serde_json::to_string(string).unwrap_or_else(|_| format!("{string:?}"))
414}
415
416enum Frame {
418 Array(Vec<Node>),
419 Object {
420 entries: Vec<(String, Node)>,
421 seen: HashMap<String, usize>,
425 key: String,
427 },
428}
429
430struct Parser<'a> {
434 src: &'a str,
435 bytes: &'a [u8],
436 pos: usize,
437}
438
439impl<'a> Parser<'a> {
440 fn new(src: &'a str) -> Self {
441 Parser {
442 src,
443 bytes: src.as_bytes(),
444 pos: 0,
445 }
446 }
447
448 fn parse_document(&mut self) -> Result<Node> {
449 let value = self.parse_value()?;
450 self.skip_whitespace();
451 if self.pos != self.bytes.len() {
452 return Err(self.error("trailing characters"));
453 }
454 Ok(value)
455 }
456
457 fn parse_value(&mut self) -> Result<Node> {
459 let mut stack: Vec<Frame> = Vec::new();
460 let mut node: Node;
461
462 'descend: loop {
463 self.skip_whitespace();
464 match self.peek() {
465 Some(b'[') => {
466 self.pos += 1;
467 self.skip_whitespace();
468 if self.peek() == Some(b']') {
469 self.pos += 1;
470 node = Node::Array(Vec::new());
471 } else {
472 stack.push(Frame::Array(Vec::new()));
473 continue 'descend;
474 }
475 }
476 Some(b'{') => {
477 self.pos += 1;
478 self.skip_whitespace();
479 if self.peek() == Some(b'}') {
480 self.pos += 1;
481 node = Node::Object(Vec::new());
482 } else {
483 let key = self.parse_key()?;
484 stack.push(Frame::Object {
485 entries: Vec::new(),
486 seen: HashMap::new(),
487 key,
488 });
489 continue 'descend;
490 }
491 }
492 _ => node = self.parse_scalar()?,
493 }
494
495 loop {
498 let Some(frame) = stack.last_mut() else {
499 return Ok(node);
500 };
501 let closing = match frame {
502 Frame::Array(items) => {
503 items.push(node);
504 b']'
505 }
506 Frame::Object { entries, seen, key } => {
507 let key = std::mem::take(key);
508 match seen.get(&key) {
509 Some(&at) => entries[at].1 = node,
510 None => {
511 seen.insert(key.clone(), entries.len());
512 entries.push((key, node));
513 }
514 }
515 b'}'
516 }
517 };
518 self.skip_whitespace();
519 match self.peek() {
520 Some(b',') => {
521 self.pos += 1;
522 if closing == b'}' {
523 let next_key = self.parse_key()?;
524 if let Some(Frame::Object { key, .. }) = stack.last_mut() {
525 *key = next_key;
526 }
527 }
528 continue 'descend;
529 }
530 Some(byte) if byte == closing => {
531 self.pos += 1;
532 node = match stack.pop() {
533 Some(Frame::Array(items)) => Node::Array(items),
534 Some(Frame::Object { entries, .. }) => Node::Object(entries),
535 None => unreachable!("the frame was just borrowed"),
536 };
537 }
538 _ if closing == b']' => return Err(self.error("expected `,` or `]`")),
539 _ => return Err(self.error("expected `,` or `}`")),
540 }
541 }
542 }
543 }
544
545 fn parse_key(&mut self) -> Result<String> {
547 self.skip_whitespace();
548 if self.peek() != Some(b'"') {
549 return Err(self.error("key must be a string"));
550 }
551 let key = self.parse_string()?;
552 self.skip_whitespace();
553 if self.peek() != Some(b':') {
554 return Err(self.error("expected `:`"));
555 }
556 self.pos += 1;
557 Ok(key)
558 }
559
560 fn parse_scalar(&mut self) -> Result<Node> {
561 match self.peek() {
562 Some(b'"') => Ok(Node::Str(self.parse_string()?)),
563 Some(b't') => {
564 self.expect_literal("true")?;
565 Ok(Node::Bool(true))
566 }
567 Some(b'f') => {
568 self.expect_literal("false")?;
569 Ok(Node::Bool(false))
570 }
571 Some(b'n') => {
572 self.expect_literal("null")?;
573 Ok(Node::Null)
574 }
575 Some(b'N') => {
579 self.expect_literal("NaN")?;
580 Ok(Node::NonFinite("NaN"))
581 }
582 Some(b'I') => {
583 self.expect_literal("Infinity")?;
584 Ok(Node::NonFinite("Infinity"))
585 }
586 Some(b'-') if self.src[self.pos..].starts_with("-Infinity") => {
587 self.pos += "-Infinity".len();
588 Ok(Node::NonFinite("-Infinity"))
589 }
590 Some(b'-' | b'0'..=b'9') => self.parse_number(),
591 Some(_) => Err(self.error("expected value")),
592 None => Err(self.error("EOF while parsing a value")),
593 }
594 }
595
596 fn parse_string(&mut self) -> Result<String> {
599 let start = self.pos;
600 let mut end = self.pos + 1;
601 loop {
602 match self.bytes.get(end) {
603 None => return Err(self.error_at(self.bytes.len(), "EOF while parsing a string")),
604 Some(b'"') => {
605 end += 1;
606 break;
607 }
608 Some(b'\\') => {
609 end += 1;
610 match self.src[end..].chars().next() {
615 Some(ch) => end += ch.len_utf8(),
616 None => {
617 return Err(
618 self.error_at(self.bytes.len(), "EOF while parsing a string")
619 )
620 }
621 }
622 }
623 Some(_) => end += 1,
626 }
627 }
628 let decoded: String = serde_json::from_str(&self.src[start..end])
629 .map_err(|error| self.error_at(start, &describe(&error)))?;
630 self.pos = end;
631 Ok(decoded)
632 }
633
634 fn parse_number(&mut self) -> Result<Node> {
637 let start = self.pos;
638 let mut end = start;
639 while matches!(
640 self.bytes.get(end),
641 Some(b'-' | b'+' | b'.' | b'e' | b'E' | b'0'..=b'9')
642 ) {
643 end += 1;
644 }
645 let token = &self.src[start..end];
646 let digits = token.as_bytes();
647 let mut i = usize::from(digits.first() == Some(&b'-'));
648 if digits.get(i) == Some(&b'0') {
649 i += 1;
650 } else {
651 let first = i;
652 while digits.get(i).is_some_and(u8::is_ascii_digit) {
653 i += 1;
654 }
655 if i == first {
656 return Err(self.error_at(start, "invalid number"));
657 }
658 }
659 let mut floating = false;
660 if digits.get(i) == Some(&b'.') {
661 floating = true;
662 i += 1;
663 let first = i;
664 while digits.get(i).is_some_and(u8::is_ascii_digit) {
665 i += 1;
666 }
667 if i == first {
668 return Err(self.error_at(start, "invalid number"));
669 }
670 }
671 if matches!(digits.get(i), Some(b'e' | b'E')) {
672 floating = true;
673 i += 1;
674 if matches!(digits.get(i), Some(b'+' | b'-')) {
675 i += 1;
676 }
677 let first = i;
678 while digits.get(i).is_some_and(u8::is_ascii_digit) {
679 i += 1;
680 }
681 if i == first {
682 return Err(self.error_at(start, "invalid number"));
683 }
684 }
685 if i != digits.len() {
686 return Err(self.error_at(start, "invalid number"));
687 }
688 self.pos = end;
689 if !floating {
690 return Ok(Node::Number(
691 if token == "-0" { "0" } else { token }.to_string(),
692 ));
693 }
694 let value: f64 = token
695 .parse()
696 .map_err(|_| self.error_at(start, "invalid number"))?;
697 if value.is_infinite() {
698 return Ok(Node::NonFinite(if value.is_sign_negative() {
699 "-Infinity"
700 } else {
701 "Infinity"
702 }));
703 }
704 let number: serde_json::Number =
705 serde_json::from_str(token).map_err(|error| self.error_at(start, &describe(&error)))?;
706 Ok(Node::Number(number.to_string()))
707 }
708
709 fn expect_literal(&mut self, literal: &str) -> Result<()> {
710 if self.src[self.pos..].starts_with(literal) {
711 self.pos += literal.len();
712 Ok(())
713 } else {
714 Err(self.error("expected value"))
715 }
716 }
717
718 fn peek(&self) -> Option<u8> {
719 self.bytes.get(self.pos).copied()
720 }
721
722 fn skip_whitespace(&mut self) {
723 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
724 self.pos += 1;
725 }
726 }
727
728 fn error(&self, message: &str) -> RichError {
729 self.error_at(self.pos, message)
730 }
731
732 fn error_at(&self, pos: usize, message: &str) -> RichError {
733 let (line, column) = self.line_column(pos);
734 RichError::Json(format!("{message} at line {line} column {column}"))
735 }
736
737 fn line_column(&self, pos: usize) -> (usize, usize) {
738 let mut pos = pos.min(self.src.len());
739 while !self.src.is_char_boundary(pos) {
740 pos -= 1;
741 }
742 let before = &self.src[..pos];
743 let line = 1 + before.matches('\n').count();
744 let column = before
745 .rsplit('\n')
746 .next()
747 .map_or(0, |tail| tail.chars().count())
748 + 1;
749 (line, column)
750 }
751}
752
753fn describe(error: &serde_json::Error) -> String {
756 let text = error.to_string();
757 match text.find(" at line ") {
758 Some(at) => text[..at].to_string(),
759 None => text,
760 }
761}
762
763#[cfg(test)]
764mod tests {
765 use super::*;
766 use crate::color::ColorSystem;
767
768 fn render(text: &str) -> String {
769 let console = Console::builder()
770 .force_terminal(true)
771 .color_system(Some(ColorSystem::Truecolor))
772 .width(40)
773 .build();
774 console.render_to_string(&Json::new(text).unwrap())
775 }
776
777 fn render_plain(text: &str, width: usize) -> String {
778 let console = Console::builder().width(width).no_color(true).build();
779 console.render_to_string(&Json::new(text).expect("valid json"))
780 }
781
782 #[test]
783 fn empty_collections_stay_inline() {
784 assert_eq!(render("{}"), "\x1b[1m{\x1b[0m\x1b[1m}\x1b[0m");
785 assert_eq!(render("[]"), "\x1b[1m[\x1b[0m\x1b[1m]\x1b[0m");
786 }
787
788 #[test]
789 fn object_with_scalars() {
790 assert_eq!(
791 render(r#"{"ok": false}"#),
792 "\x1b[1m{\x1b[0m\n \x1b[1;34m\"ok\"\x1b[0m: \x1b[3;91mfalse\x1b[0m\n\x1b[1m}\x1b[0m"
793 );
794 }
795
796 #[test]
797 fn invalid_json_errors() {
798 assert!(Json::new("{not json}").is_err());
799 }
800
801 #[test]
802 fn non_ascii_stays_utf8_in_input_order() {
803 let out = render("{\"name\": \"caf\u{e9}\", \"emoji\": \"\u{2764}\"}");
807 assert!(out.contains("caf\u{e9}"), "café stays UTF-8: {out:?}");
808 assert!(out.contains('\u{2764}'), "heart stays UTF-8");
809 let name_at = out.find("name").expect("name key present");
810 let emoji_at = out.find("emoji").expect("emoji key present");
811 assert!(name_at < emoji_at, "keys keep input order");
812 }
813
814 #[test]
815 fn a_long_value_is_wrapped_rather_than_cropped() {
816 let payload = format!("{{\"k\": \"{}\"}}", "y".repeat(120));
819 let out = render_plain(&payload, 40);
820 assert_eq!(
821 out.matches('y').count(),
822 120,
823 "characters were dropped:
824{out}"
825 );
826 }
827
828 #[test]
837 fn wrapping_breaks_at_word_boundaries() {
838 let payload = r#"{"k": "the quick brown fox jumps over the lazy dog and keeps running for a very long time indeed"}"#;
839 assert_eq!(
840 render_plain(payload, 40),
841 "{\n \"k\": \"the quick brown fox jumps over \n\
842 the lazy dog and keeps running for a \n\
843 very long time indeed\"\n}"
844 );
845 }
846
847 #[cfg(feature = "json-escape-safe")]
848 #[test]
849 fn adjusted_escape_boundaries_preserve_the_remaining_payload() {
850 let input = format!(r#"{{"v":"aa\u0001{}"}}"#, "b".repeat(40));
851 for width in 6..=20 {
852 let output = Console::builder()
853 .width(width)
854 .force_terminal(false)
855 .build()
856 .render_to_string(&Json::new(&input).unwrap().escape_safe(true));
857 assert_eq!(output.matches('b').count(), 40, "width {width}: {output:?}");
858 }
859 }
860
861 #[cfg(feature = "json-escape-safe")]
862 #[test]
863 fn wrapping_keeps_json_escapes_atomic_at_narrow_widths() {
864 let payload = r#"{"v":"a\"b\\c\nd\u0001e"}"#;
865 for width in 8..=14 {
866 let output = Console::builder()
867 .width(width)
868 .force_terminal(false)
869 .build()
870 .render_to_string(&Json::new(payload).unwrap().escape_safe(true));
871 for line in output.lines() {
872 let bytes = line.as_bytes();
873 let mut index = 0;
874 while index < bytes.len() {
875 if bytes[index] != b'\\' {
876 index += 1;
877 continue;
878 }
879 assert!(
880 index + 1 < bytes.len(),
881 "split escape at width {width}: {output:?}"
882 );
883 if bytes[index + 1] == b'u' {
884 assert!(
885 index + 6 <= bytes.len(),
886 "split unicode escape at width {width}: {output:?}"
887 );
888 index += 6;
889 } else {
890 index += 2;
891 }
892 }
893 }
894 }
895 }
896
897 #[cfg(feature = "json-escape-safe")]
898 #[test]
899 fn escape_folding_preserves_bytes_even_below_the_escape_width() {
900 let payload = r#"{"v":"a\"b\\c\nd\u0001eeeeeeeeeeee"}"#;
901 let wide = Console::builder()
902 .width(100)
903 .force_terminal(false)
904 .build()
905 .render_to_string(&Json::new(payload).unwrap().escape_safe(true))
906 .replace('\n', "");
907 for width in 1..=20 {
908 let output = Console::builder()
909 .width(width)
910 .force_terminal(false)
911 .build()
912 .render_to_string(&Json::new(payload).unwrap().escape_safe(true));
913 assert_eq!(output.replace('\n', ""), wide, "width {width}");
914 assert!(output
915 .lines()
916 .all(|line| crate::cells::cell_len(line) <= width));
917 }
918 }
919
920 #[cfg(feature = "json-escape-safe")]
921 #[test]
922 fn escape_cropping_never_emits_a_partial_escape() {
923 let payload = r#""a\"b\\c\nd\u0001eeee""#;
924 for width in 1..=24 {
925 let output = Console::builder()
926 .width(width)
927 .force_terminal(false)
928 .build()
929 .render_to_string(&Json::new(payload).unwrap().no_wrap(true).escape_safe(true));
930 let mut chars = output.chars();
931 while let Some(c) = chars.next() {
932 if c == '\\' {
933 let next = chars.next().expect("complete short escape");
934 if next == 'u' {
935 for _ in 0..4 {
936 assert!(chars.next().is_some_and(|c| c.is_ascii_hexdigit()));
937 }
938 }
939 }
940 }
941 }
942 }
943
944 #[test]
954 fn a_nested_document_is_cropped_rather_than_wrapped() {
955 let payload = r#"{"k": "the quick brown fox jumps over the lazy dog and keeps running for a very long time indeed"}"#;
956 let console = Console::builder().width(40).no_color(true).build();
957 let json = Json::new(payload).expect("valid json").no_wrap(true);
958 assert_eq!(
959 console.render_to_string(&json),
960 "{\n \"k\": \"the quick brown fox jumps over t\n}"
961 );
962
963 assert_eq!(
966 render_plain(payload, 40),
967 "{\n \"k\": \"the quick brown fox jumps over \n\
968 the lazy dog and keeps running for a \n\
969 very long time indeed\"\n}"
970 );
971 }
972
973 #[test]
977 fn cropping_never_splits_a_wide_character() {
978 let console = Console::builder().width(12).no_color(true).build();
979 let json = Json::new("{\"k\": \"\u{1f306}\u{1f306}\u{1f306}\"}")
980 .expect("valid json")
981 .no_wrap(true);
982 for line in console.render_to_string(&json).lines() {
983 assert!(
984 crate::cells::cell_len(line) <= 12,
985 "line {line:?} overflows the crop"
986 );
987 }
988 }
989
990 #[test]
994 fn floats_round_trip_exactly() {
995 for literal in [
996 "-938371.9565467801",
997 "0.1",
998 "1.7976931348623157e308",
999 "5e-324",
1000 "3.141592653589793",
1001 ] {
1002 let out = render_plain(&format!("{{\"v\": {literal}}}"), 120);
1003 let rendered: String = out
1004 .split(':')
1005 .nth(1)
1006 .expect("a value after the key")
1007 .trim()
1008 .trim_end_matches(['}', ' ', '\n'])
1009 .to_string();
1010 let want: f64 = literal.parse().expect("literal parses");
1011 let got: f64 = rendered
1012 .parse()
1013 .unwrap_or_else(|_| panic!("rendered {rendered:?}"));
1014 assert_eq!(
1015 got.to_bits(),
1016 want.to_bits(),
1017 "{literal} rendered as {rendered} — a different double"
1018 );
1019 }
1020 }
1021
1022 #[test]
1026 fn deep_nesting_is_not_rejected() {
1027 for depth in [128, 129, 200, 1000] {
1028 let payload = format!("{}1{}", "[".repeat(depth), "]".repeat(depth));
1029 let json = Json::new(&payload)
1030 .unwrap_or_else(|error| panic!("depth {depth} rejected: {error}"));
1031 let console = Console::builder()
1032 .width(4 * depth + 8)
1033 .no_color(true)
1034 .build();
1035 let out = console.render_to_string(&json);
1036 assert_eq!(
1037 out.matches('[').count(),
1038 depth,
1039 "depth {depth} did not render every level"
1040 );
1041 }
1042 }
1043
1044 #[test]
1048 fn very_deep_nesting_does_not_overflow_the_stack() {
1049 let depth = 100_000;
1050 let payload = format!("{}1{}", "[".repeat(depth), "]".repeat(depth));
1051 let json = Json::new(&payload).expect("deep document parses");
1052 drop(json);
1053 }
1054
1055 #[test]
1063 fn non_finite_numbers_render_unstyled() {
1064 assert_eq!(
1065 render(r#"{"a": NaN, "b": Infinity, "c": -Infinity, "d": 1.5}"#),
1066 "\x1b[1m{\x1b[0m\n \x1b[1;34m\"a\"\x1b[0m: NaN,\n \x1b[1;34m\"b\"\x1b[0m: \
1067 Infinity,\n \x1b[1;34m\"c\"\x1b[0m: -Infinity,\n \x1b[1;34m\"d\"\x1b[0m: \
1068 \x1b[1;36m1.5\x1b[0m\n\x1b[1m}\x1b[0m"
1069 );
1070 for rejected in [r#"{"a": nan}"#, r#"{"a": inf}"#, r#"{"a": -inf}"#] {
1072 assert!(Json::new(rejected).is_err(), "{rejected} should not parse");
1073 }
1074 }
1075
1076 #[test]
1079 fn acceptance_matches_serde_json() {
1080 let samples = [
1081 "{}",
1082 "[]",
1083 " {\t\"a\" :\n1 } ",
1084 r#"{"a": 1, "a": 2}"#,
1085 r#"{"a": [1, {"b": null}], "c": "x"}"#,
1086 "0",
1087 "-0",
1088 "0.0",
1089 "1e10",
1090 "1E+10",
1091 "1e-7",
1092 "12345678901234567890",
1093 "-12345678901234567890123456789012345",
1094 "01",
1095 "1.",
1096 ".1",
1097 "+1",
1098 "1e",
1099 "-",
1100 "--1",
1101 "-i",
1102 "-Inf",
1103 "Infinit",
1104 "NAN",
1105 "1 2",
1106 "",
1107 " ",
1108 "{",
1109 "[",
1110 "]",
1111 "}",
1112 "[,]",
1113 "[1,]",
1114 r#"{"a": 1,}"#,
1115 r#"{a: 1}"#,
1116 r#"{'a': 1}"#,
1117 r#"{"a" 1}"#,
1118 "[1 2]",
1119 "truex",
1120 "tru",
1121 "nul",
1122 r#""unterminated"#,
1123 r#""\q""#,
1124 r#""é""#,
1125 r#""😀""#,
1126 r#""\ud800""#,
1127 "\"raw\nnewline\"",
1128 r#""café ❤""#,
1129 "\"\u{e9}\\\"",
1130 "\u{feff}{}",
1131 "[[[[1]]]]",
1132 ];
1133 for sample in samples {
1134 let ours = Json::new(sample).is_ok();
1135 let theirs = serde_json::from_str::<serde_json::Value>(sample).is_ok();
1136 assert_eq!(ours, theirs, "disagreed about {sample:?}");
1137 }
1138 }
1139
1140 #[test]
1141 fn python_numbers_preserve_large_integers_and_overflow() {
1142 for number in [
1143 "1234567890123456789012345678901234567890",
1144 "-1234567890123456789012345678901234567890",
1145 ] {
1146 assert_eq!(render_plain(number, 100), number);
1147 }
1148 assert_eq!(render_plain("-0", 100), "0");
1149 assert_eq!(render_plain("1e400", 100), "Infinity");
1150 assert_eq!(render_plain("-1e999", 100), "-Infinity");
1151 for invalid in ["01", "-01", "1.e2", "1e+", "1e400x", "--1", "1+2", ".1"] {
1152 assert!(Json::new(invalid).is_err(), "accepted {invalid}");
1153 }
1154 }
1155
1156 #[test]
1162 fn the_parsed_tree_matches_serde_json() {
1163 let samples = [
1164 r#"{"name": "Alice", "age": 30, "admin": true, "tags": ["a", "b"], "meta": null}"#,
1165 r#"{"a": 1, "b": 2, "a": 3}"#,
1166 r#"{"a": {"b": {"c": [1, [], {}, [[2]]]}}}"#,
1167 r#"{"k": "A\t\"x\"A\\\/é"}"#,
1168 r#"[0, -0.5, 1e10, 1E+10, 1e-7, 12345678901234567890, 1.7976931348623157e308]"#,
1169 r#"{"café": "❤", "": ""}"#,
1170 "[]",
1171 "{}",
1172 "\"top level\"",
1173 "1234",
1174 ];
1175 for sample in samples {
1176 let reference: serde_json::Value =
1177 serde_json::from_str(sample).expect("sample is valid JSON");
1178 assert_eq!(
1179 render_plain(sample, 10_000),
1180 serde_json::to_string_pretty(&reference).expect("value re-serialises"),
1181 "diverged on {sample}"
1182 );
1183 }
1184 }
1185
1186 #[test]
1189 fn a_repeated_key_keeps_its_position_and_last_value() {
1190 assert_eq!(
1191 render_plain(r#"{"a": 1, "b": 2, "a": 3}"#, 40),
1192 "{\n \"a\": 3,\n \"b\": 2\n}"
1193 );
1194 }
1195
1196 #[test]
1199 fn escapes_are_re_encoded_like_dumps() {
1200 assert_eq!(
1201 render_plain(r#"{"k": "A\t\"x\""}"#, 60),
1202 "{\n \"k\": \"A\\t\\\"x\\\"\"\n}"
1203 );
1204 }
1205}