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