1use crate::WriteError;
47use crate::document::{Doc, Value};
48use crate::error::{OmnistError, ParseError};
49use crate::formats::float_fmt;
50use crate::formats::int_cap::{MAX_INT_DIGITS, over_cap_message};
51use crate::formats::string_escape::{JSON_ESCAPES, write_quoted};
52use crate::formats::textpos::line_col_bytes;
53use crate::report::{Severity, WriteReport};
54use indexmap::IndexMap;
55
56pub fn read_json(text: &str) -> Result<Doc, OmnistError> {
69 let mut p = Parser::new(text);
70 p.skip_ws();
71 let value = p.parse_value()?;
72 p.skip_ws();
73 if p.pos < p.n {
74 return Err(p
75 .error_at(
76 p.pos,
77 "unexpected trailing data after JSON value".to_string(),
78 )
79 .into());
80 }
81 Ok(Doc::of(&value)?)
82}
83
84pub fn write_json(
97 doc: &Doc,
98 indent: Option<usize>,
99 strict: bool,
100 report: Option<&mut WriteReport>,
101) -> Result<String, WriteError> {
102 let grouped = doc.to_grouped();
103 if let Some((path, x)) = find_special_float(&grouped) {
104 return Err(crate::report::unsupported_value_error(
105 &path,
106 format!("{x} has no JSON token (JSON's grammar has no NaN/Infinity literal)"),
107 ));
108 }
109 let mut rep = check_json_grouped(&grouped);
110 add_interleaving_diagnostic(doc, &mut rep);
111 let mut out = String::new();
112 write_value(&grouped, indent, 0, &mut out);
113 crate::report::finish_write(out, rep, strict, report)
114}
115
116fn find_special_float(grouped: &Value) -> Option<(String, f64)> {
120 let mut path = String::from("$");
121 let mut found: Option<(String, f64)> = None;
122 crate::formats::visit_grouped(grouped, &mut path, &mut |visited, path| {
123 if found.is_some() {
124 return;
125 }
126 let crate::formats::Visited::Node { value } = visited else {
127 return;
128 };
129 if let Value::Float(x) = value
130 && (x.is_nan() || x.is_infinite())
131 {
132 found = Some((path.to_string(), *x));
133 }
134 });
135 found
136}
137
138fn add_interleaving_diagnostic(doc: &Doc, rep: &mut WriteReport) {
144 if doc.has_interleaving_loss() {
145 rep.add(
146 "$",
147 "format.interleaving-lost",
148 "cross-label interleaving could not be written; same-label edges were grouped",
149 Severity::Warning,
150 );
151 }
152}
153
154pub fn check_json(doc: &Doc) -> WriteReport {
162 let grouped = doc.to_grouped();
163 let mut rep = check_json_grouped(&grouped);
164 add_interleaving_diagnostic(doc, &mut rep);
165 rep
166}
167
168fn check_json_grouped(grouped: &Value) -> WriteReport {
169 let mut rep = WriteReport::new();
170 let mut path = String::from("$");
171 crate::formats::visit_grouped(grouped, &mut path, &mut |visited, path| {
172 let crate::formats::Visited::Node { value } = visited else {
173 return;
174 };
175 match value {
176 Value::Float(x) if x.is_nan() || x.is_infinite() => {
182 rep.add(
183 path,
184 "write.unsupported-value",
185 format!("{x} has no JSON token (JSON's grammar has no NaN/Infinity literal)"),
186 Severity::Error,
187 );
188 }
189 Value::Date(_) | Value::Time(_) | Value::Datetime(_) => {
195 rep.add(
196 path,
197 "format.temporal-stringified",
198 "date/time/datetime has no native JSON literal; wrote as an ISO-8601 string"
199 .to_string(),
200 Severity::Warning,
201 );
202 }
203 _ => {}
204 }
205 });
206 rep
207}
208
209pub(crate) struct Json;
216
217impl crate::formats::Codec for Json {
218 const NAME: &'static str = "json";
219
220 fn read(text: &str) -> Result<Doc, OmnistError> {
221 read_json(text)
222 }
223
224 fn write(doc: &Doc) -> Result<String, OmnistError> {
225 write_json(doc, None, false, None).map_err(Into::into)
226 }
227
228 fn check(doc: &Doc) -> WriteReport {
229 check_json(doc)
230 }
231}
232
233fn write_value(v: &Value, indent: Option<usize>, level: usize, out: &mut String) {
234 match v {
235 Value::Null => out.push_str("null"),
236 Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
237 Value::Int(i) => out.push_str(&i.to_string()),
238 Value::Float(x) => write_float(*x, out),
239 Value::Str(s) | Value::Date(s) | Value::Time(s) | Value::Datetime(s) => {
244 write_json_string(s, out)
245 }
246 Value::Array(items) => write_seq(items.iter(), '[', ']', indent, level, out, write_value),
247 Value::Object(map) => write_seq(
248 map.iter(),
249 '{',
250 '}',
251 indent,
252 level,
253 out,
254 |(k, val), indent, level, out| {
255 write_json_string(k, out);
256 out.push_str(": ");
257 write_value(val, indent, level, out);
258 },
259 ),
260 }
261}
262
263fn write_seq<I, T>(
264 items: I,
265 open: char,
266 close: char,
267 indent: Option<usize>,
268 level: usize,
269 out: &mut String,
270 mut write_item: impl FnMut(T, Option<usize>, usize, &mut String),
271) where
272 I: ExactSizeIterator<Item = T>,
273{
274 if items.len() == 0 {
275 out.push(open);
276 out.push(close);
277 return;
278 }
279 out.push(open);
280 let child_level = level + 1;
281 let mut first = true;
282 for item in items {
283 if !first {
284 out.push(',');
285 if indent.is_none() {
286 out.push(' ');
287 }
288 }
289 first = false;
290 if let Some(n) = indent {
291 out.push('\n');
292 out.push_str(&" ".repeat(n * child_level));
293 }
294 write_item(item, indent, child_level, out);
295 }
296 if let Some(n) = indent {
297 out.push('\n');
298 out.push_str(&" ".repeat(n * level));
299 }
300 out.push(close);
301}
302
303fn write_float(x: f64, out: &mut String) {
313 float_fmt::write_float(x, "NaN", "Infinity", "-Infinity", out);
314}
315
316fn write_json_string(s: &str, out: &mut String) {
317 write_quoted(s, &JSON_ESCAPES, out);
318}
319
320struct Parser<'a> {
323 text: &'a str,
324 n: usize,
325 pos: usize,
326 depth: usize,
327}
328
329impl<'a> Parser<'a> {
330 fn new(text: &'a str) -> Self {
331 let n = text.len();
339 Parser {
340 text,
341 n,
342 pos: 0,
343 depth: 0,
344 }
345 }
346
347 fn error_at(&self, pos: usize, msg: String) -> ParseError {
348 let (line, col) = line_col_bytes(self.text, pos);
349 ParseError::new(line, col, format!("invalid JSON: {msg}"))
350 }
351
352 fn char_at(&self, at: usize) -> Option<char> {
357 self.text.get(at..)?.chars().next()
358 }
359
360 fn peek(&self) -> Option<char> {
361 self.char_at(self.pos)
362 }
363
364 fn skip_ws(&mut self) {
365 while matches!(
366 self.peek(),
367 Some(' ') | Some('\t') | Some('\n') | Some('\r')
368 ) {
369 self.pos += 1;
370 }
371 }
372
373 fn expect(&mut self, c: char) -> Result<(), ParseError> {
374 if self.peek() == Some(c) {
375 self.pos += c.len_utf8();
376 Ok(())
377 } else {
378 Err(self.error_at(self.pos, format!("expected {c:?}")))
379 }
380 }
381
382 fn matches_word(&self, word: &str) -> bool {
387 debug_assert!(
388 word.is_ascii(),
389 "matches_word is only used with ASCII keywords"
390 );
391 let bytes = self.text.as_bytes();
392 word.bytes()
393 .enumerate()
394 .all(|(i, b)| bytes.get(self.pos + i) == Some(&b))
395 }
396
397 fn parse_value(&mut self) -> Result<Value, ParseError> {
398 self.skip_ws();
399 match self.peek() {
400 None => Err(self.error_at(self.pos, "unexpected end of input".to_string())),
401 Some('{') => self.parse_object(),
402 Some('[') => self.parse_array(),
403 Some('"') => Ok(Value::Str(self.parse_string()?)),
404 Some('t') if self.matches_word("true") => {
405 self.pos += 4;
406 Ok(Value::Bool(true))
407 }
408 Some('f') if self.matches_word("false") => {
409 self.pos += 5;
410 Ok(Value::Bool(false))
411 }
412 Some('n') if self.matches_word("null") => {
413 self.pos += 4;
414 Ok(Value::Null)
415 }
416 Some('N') if self.matches_word("NaN") => {
417 self.pos += 3;
418 Ok(Value::Float(f64::NAN))
419 }
420 Some('I') if self.matches_word("Infinity") => {
421 self.pos += 8;
422 Ok(Value::Float(f64::INFINITY))
423 }
424 Some('-') if self.matches_word("-Infinity") => {
425 self.pos += 9;
426 Ok(Value::Float(f64::NEG_INFINITY))
427 }
428 Some(c) if c == '-' || c.is_ascii_digit() => self.parse_number(),
429 Some(c) => Err(self.error_at(self.pos, format!("unexpected character {c:?}"))),
430 }
431 }
432
433 fn parse_object(&mut self) -> Result<Value, ParseError> {
434 self.depth += 1;
435 if self.depth > crate::document::MAX_DEPTH {
436 return Err(self.error_at(
437 self.pos,
438 format!(
439 "nesting exceeds the maximum depth ({})",
440 crate::document::MAX_DEPTH
441 ),
442 ));
443 }
444 self.expect('{')?;
445 let mut map: IndexMap<String, Value> = IndexMap::new();
446 self.skip_ws();
447 if self.peek() == Some('}') {
448 self.pos += 1;
449 self.depth -= 1;
450 return Ok(Value::Object(map));
451 }
452 loop {
453 self.skip_ws();
454 if self.peek() != Some('"') {
455 return Err(self.error_at(self.pos, "expected string key".to_string()));
456 }
457 let key = self.parse_string()?;
458 self.skip_ws();
459 self.expect(':')?;
460 let value = self.parse_value()?;
461 map.insert(key, value);
466 self.skip_ws();
467 match self.peek() {
468 Some(',') => {
469 self.pos += 1;
470 }
471 Some('}') => {
472 self.pos += 1;
473 break;
474 }
475 _ => return Err(self.error_at(self.pos, "expected ',' or '}'".to_string())),
476 }
477 }
478 self.depth -= 1;
479 Ok(Value::Object(map))
480 }
481
482 fn parse_array(&mut self) -> Result<Value, ParseError> {
483 self.depth += 1;
484 if self.depth > crate::document::MAX_DEPTH {
485 return Err(self.error_at(
486 self.pos,
487 format!(
488 "nesting exceeds the maximum depth ({})",
489 crate::document::MAX_DEPTH
490 ),
491 ));
492 }
493 self.expect('[')?;
494 let mut items = Vec::new();
495 self.skip_ws();
496 if self.peek() == Some(']') {
497 self.pos += 1;
498 self.depth -= 1;
499 return Ok(Value::Array(items));
500 }
501 loop {
502 let v = self.parse_value()?;
503 items.push(v);
504 self.skip_ws();
505 match self.peek() {
506 Some(',') => {
507 self.pos += 1;
508 }
509 Some(']') => {
510 self.pos += 1;
511 break;
512 }
513 _ => return Err(self.error_at(self.pos, "expected ',' or ']'".to_string())),
514 }
515 }
516 self.depth -= 1;
517 Ok(Value::Array(items))
518 }
519
520 fn parse_string(&mut self) -> Result<String, ParseError> {
521 self.expect('"')?;
522 let mut s = String::new();
523 loop {
524 match self.peek() {
525 None => return Err(self.error_at(self.pos, "unterminated string".to_string())),
526 Some('"') => {
527 self.pos += 1;
528 break;
529 }
530 Some('\\') => {
531 self.pos += 1;
532 match self.peek() {
533 Some('"') => {
534 s.push('"');
535 self.pos += 1;
536 }
537 Some('\\') => {
538 s.push('\\');
539 self.pos += 1;
540 }
541 Some('/') => {
542 s.push('/');
543 self.pos += 1;
544 }
545 Some('b') => {
546 s.push('\u{08}');
547 self.pos += 1;
548 }
549 Some('f') => {
550 s.push('\u{0c}');
551 self.pos += 1;
552 }
553 Some('n') => {
554 s.push('\n');
555 self.pos += 1;
556 }
557 Some('r') => {
558 s.push('\r');
559 self.pos += 1;
560 }
561 Some('t') => {
562 s.push('\t');
563 self.pos += 1;
564 }
565 Some('u') => {
566 self.pos += 1;
567 let hi = self.parse_hex4()?;
568 if (0xD800..=0xDBFF).contains(&hi) {
569 if self.peek() == Some('\\')
570 && self.char_at(self.pos + 1) == Some('u')
571 {
572 self.pos += 2;
573 let lo = self.parse_hex4()?;
574 if (0xDC00..=0xDFFF).contains(&lo) {
575 let c = 0x10000 + (hi - 0xD800) * 0x400 + (lo - 0xDC00);
576 s.push(char::from_u32(c).expect(
589 "a well-formed UTF-16 surrogate pair always \
590 combines to a valid supplementary-plane char",
591 ));
592 } else {
593 return Err(self.error_at(
594 self.pos,
595 "invalid low surrogate".to_string(),
596 ));
597 }
598 } else {
599 return Err(self.error_at(
600 self.pos,
601 "unpaired high surrogate".to_string(),
602 ));
603 }
604 } else if (0xDC00..=0xDFFF).contains(&hi) {
605 return Err(
606 self.error_at(self.pos, "unpaired low surrogate".to_string())
607 );
608 } else {
609 s.push(char::from_u32(hi).expect(
620 "a 4-hex-digit \\u escape outside the surrogate range is \
621 always a valid BMP char",
622 ));
623 }
624 }
625 _ => return Err(self.error_at(self.pos, "invalid escape".to_string())),
626 }
627 }
628 Some(c) if (c as u32) < 0x20 => {
629 return Err(self.error_at(self.pos, "control character in string".to_string()));
630 }
631 Some(c) => {
632 s.push(c);
633 self.pos += c.len_utf8();
634 }
635 }
636 }
637 Ok(s)
638 }
639
640 fn parse_hex4(&mut self) -> Result<u32, ParseError> {
641 let mut v: u32 = 0;
642 for _ in 0..4 {
643 let c = self.peek().ok_or_else(|| {
644 self.error_at(self.pos, "unterminated unicode escape".to_string())
645 })?;
646 let d = c.to_digit(16).ok_or_else(|| {
647 self.error_at(self.pos, "invalid hex digit in unicode escape".to_string())
648 })?;
649 v = v * 16 + d;
650 self.pos += 1;
654 }
655 Ok(v)
656 }
657
658 fn parse_number(&mut self) -> Result<Value, ParseError> {
663 let start = self.pos;
664 if self.peek() == Some('-') {
665 self.pos += 1;
666 }
667 let int_start = self.pos;
668 if self.peek() == Some('0') {
669 self.pos += 1;
670 } else if self.peek().is_some_and(|c| c.is_ascii_digit()) {
671 while self.peek().is_some_and(|c| c.is_ascii_digit()) {
672 self.pos += 1;
673 }
674 } else {
675 return Err(self.error_at(self.pos, "invalid number literal".to_string()));
676 }
677 debug_assert!(
678 self.pos > int_start,
679 "the '0' and digit-run branches above both advance pos by at least 1"
680 );
681 let bytes = self.text.as_bytes();
685 let byte_at = |p: usize| bytes.get(p).copied();
686 let mut is_float = false;
687 if self.peek() == Some('.') {
688 let frac_start = self.pos + 1;
689 let mut p = frac_start;
690 while byte_at(p).is_some_and(|b| b.is_ascii_digit()) {
691 p += 1;
692 }
693 if p > frac_start {
694 is_float = true;
695 self.pos = p;
696 }
697 }
698 if matches!(self.peek(), Some('e') | Some('E')) {
699 let mut p = self.pos + 1;
700 if matches!(byte_at(p), Some(b'+') | Some(b'-')) {
701 p += 1;
702 }
703 let exp_start = p;
704 while byte_at(p).is_some_and(|b| b.is_ascii_digit()) {
705 p += 1;
706 }
707 if p > exp_start {
708 is_float = true;
709 self.pos = p;
710 }
711 }
712 let text: &str = &self.text[start..self.pos];
713 if is_float {
714 let v: f64 = text
715 .parse()
716 .expect("scanner only emits number-shaped text, which f64::from_str always parses");
717 Ok(Value::Float(v))
718 } else {
719 let digits = &text[if text.starts_with('-') { 1 } else { 0 }..];
720 if digits.len() > MAX_INT_DIGITS {
721 return Err(self.error_at(start, over_cap_message("", digits.len())));
722 }
723 let v = num_bigint::BigInt::parse_bytes(text.as_bytes(), 10).expect(
727 "scanner only emits digit-shaped text, which BigInt::parse_bytes always parses",
728 );
729 Ok(Value::Int(v))
730 }
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737 use crate::document::{Doc, Scalar, Value};
738 use crate::report::Severity;
739
740 fn obj(pairs: Vec<(&str, Value)>) -> Value {
741 Value::Object(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
742 }
743
744 #[test]
747 fn reads_object_with_scalars() {
748 let doc = read_json(r#"{"a": 1, "b": "s", "c": true, "d": null, "e": 1.5}"#).unwrap();
749 let root = doc.root();
750 assert_eq!(
751 *root.get_one("a").unwrap().value().unwrap(),
752 Scalar::Int((1).into())
753 );
754 assert_eq!(
755 *root.get_one("b").unwrap().value().unwrap(),
756 Scalar::Str("s".to_string())
757 );
758 assert_eq!(
759 *root.get_one("c").unwrap().value().unwrap(),
760 Scalar::Bool(true)
761 );
762 assert_eq!(*root.get_one("d").unwrap().value().unwrap(), Scalar::Null);
763 assert_eq!(
764 *root.get_one("e").unwrap().value().unwrap(),
765 Scalar::Float(1.5)
766 );
767 }
768
769 #[test]
770 fn reads_false_literal() {
771 let doc = read_json(r#"{"c": false}"#).unwrap();
772 assert_eq!(
773 *doc.root().get_one("c").unwrap().value().unwrap(),
774 Scalar::Bool(false)
775 );
776 }
777
778 #[test]
779 fn reads_array_as_repeated_edges() {
780 let doc = read_json(r#"{"m": [1, 2, 3]}"#).unwrap();
781 let root = doc.root();
782 let ms = root.get("m");
783 assert_eq!(ms.len(), 3);
784 assert_eq!(*ms[0].value().unwrap(), Scalar::Int((1).into()));
785 assert_eq!(*ms[2].value().unwrap(), Scalar::Int((3).into()));
786 }
787
788 #[test]
789 fn reads_empty_array_literal_as_no_edges() {
790 let doc = read_json(r#"{"m": [], "n": 1}"#).unwrap();
791 assert!(doc.root().get("m").is_empty());
792 }
793
794 #[test]
795 fn reads_nested_object() {
796 let doc = read_json(r#"{"a": {"b": {"c": 1}}}"#).unwrap();
797 let root = doc.root();
798 let a = root.get_one("a").unwrap();
799 let b = a.get_one("b").unwrap();
800 assert_eq!(
801 *b.get_one("c").unwrap().value().unwrap(),
802 Scalar::Int((1).into())
803 );
804 }
805
806 #[test]
807 fn bare_top_level_array_is_a_document_error_not_a_parse_error() {
808 let err = read_json("[1, 2, 3]").unwrap_err();
809 assert!(matches!(err, OmnistError::Document(_)), "got {err:?}");
810 }
811
812 #[test]
813 fn array_of_arrays_is_a_document_error() {
814 let err = read_json(r#"{"m": [[1, 2]]}"#).unwrap_err();
815 assert!(matches!(err, OmnistError::Document(_)), "got {err:?}");
816 }
817
818 #[test]
819 fn invalid_json_syntax_is_a_parse_error() {
820 let err = read_json("{not json}").unwrap_err();
821 assert!(matches!(err, OmnistError::Parse(_)), "got {err:?}");
822 }
823
824 #[test]
825 fn trailing_data_after_a_value_is_a_parse_error() {
826 let err = read_json("1 2").unwrap_err();
827 assert!(matches!(err, OmnistError::Parse(_)), "got {err:?}");
828 }
829
830 #[test]
831 fn nesting_past_max_depth_is_a_parse_error() {
832 let mut text = String::new();
833 for _ in 0..=crate::document::MAX_DEPTH {
834 text.push_str(r#"{"a":"#);
835 }
836 text.push('1');
837 for _ in 0..=crate::document::MAX_DEPTH {
838 text.push('}');
839 }
840 let err = read_json(&text).unwrap_err();
841 assert!(matches!(err, OmnistError::Parse(_)), "got {err:?}");
842 assert!(err.to_string().contains("maximum depth"));
843 }
844
845 #[test]
846 fn duplicate_object_keys_last_value_wins_first_position_kept() {
847 let doc = read_json(r#"{"a": 1, "b": 2, "a": 3}"#).unwrap();
848 let root = doc.root();
849 assert_eq!(root.labels(), vec!["a".to_string(), "b".to_string()]);
850 assert_eq!(
851 *root.get_one("a").unwrap().value().unwrap(),
852 Scalar::Int((3).into())
853 );
854 }
855
856 #[test]
857 fn reads_string_escapes_and_unicode_escape() {
858 let doc = read_json(r#"{"s": "a\n\r\t\"\\é"}"#).unwrap();
859 let v = doc.root().get_one("s").unwrap();
860 assert_eq!(
861 *v.value().unwrap(),
862 Scalar::Str("a\n\r\t\"\\\u{e9}".to_string())
863 );
864 }
865
866 #[test]
867 fn reads_plain_unicode_escape() {
868 const BSL: char = '\u{5c}';
869 let input = format!("{{\"s\": \"{BSL}u0041\"}}");
870 let doc = read_json(&input).unwrap();
871 let v = doc.root().get_one("s").unwrap();
872 assert_eq!(*v.value().unwrap(), Scalar::Str("A".to_string()));
873 }
874
875 #[test]
876 fn reads_surrogate_pair_escape() {
877 let doc = read_json(r#"{"s": "😀"}"#).unwrap();
881 let v = doc.root().get_one("s").unwrap();
882 assert_eq!(*v.value().unwrap(), Scalar::Str("\u{1F600}".to_string()));
883 }
884
885 #[test]
886 fn reads_surrogate_pair_written_as_two_u_escapes() {
887 const BSL: char = '\u{5c}';
891 let input = format!("{{\"s\": \"{BSL}ud83d{BSL}ude00\"}}");
892 let doc = read_json(&input).unwrap();
893 let v = doc.root().get_one("s").unwrap();
894 assert_eq!(*v.value().unwrap(), Scalar::Str("\u{1F600}".to_string()));
895 }
896
897 #[test]
898 fn reads_bare_nan_and_infinity_tokens() {
899 let doc = read_json(r#"{"a": NaN, "b": Infinity, "c": -Infinity}"#).unwrap();
902 let root = doc.root();
903 assert!(
904 matches!(root.get_one("a").unwrap().value().unwrap(), Scalar::Float(x) if x.is_nan())
905 );
906 assert_eq!(
907 *root.get_one("b").unwrap().value().unwrap(),
908 Scalar::Float(f64::INFINITY)
909 );
910 assert_eq!(
911 *root.get_one("c").unwrap().value().unwrap(),
912 Scalar::Float(f64::NEG_INFINITY)
913 );
914 }
915
916 #[test]
917 fn integer_literal_under_digit_cap_but_over_i64_range_parses() {
918 let text = format!(r#"{{"a": {}}}"#, "9".repeat(20));
923 let doc = read_json(&text).unwrap();
924 let value = doc.root().child("a").unwrap().value().unwrap();
925 assert_eq!(
926 value,
927 &Scalar::Int(num_bigint::BigInt::parse_bytes(b"99999999999999999999", 10).unwrap())
928 );
929 }
930
931 #[test]
932 fn integer_literal_over_digit_cap_is_rejected_before_range_check() {
933 let text = format!(r#"{{"a": {}}}"#, "9".repeat(MAX_INT_DIGITS + 1));
934 let err = read_json(&text).unwrap_err();
935 assert!(
936 matches!(&err, OmnistError::Parse(e) if e.message.contains("4300-digit")),
937 "got {err:?}"
938 )
939 }
940
941 #[test]
942 fn integer_literal_exactly_at_digit_cap_parses_not_digit_cap_error() {
943 let text = format!(r#"{{"a": {}}}"#, "9".repeat(MAX_INT_DIGITS));
947 let doc = read_json(&text).unwrap();
948 let value = doc.root().child("a").unwrap().value().unwrap();
949 assert!(
950 matches!(value, Scalar::Int(i) if i.to_string().len() == MAX_INT_DIGITS),
951 "got {value:?}"
952 );
953 }
954
955 #[test]
956 fn whitespace_and_negative_zero_and_exponent_numbers_read() {
957 let doc = read_json(" {\n\"a\" : 1e3,\n\"b\": -0.5, \"c\": 2E-2\t} ").unwrap();
958 let root = doc.root();
959 assert_eq!(
960 *root.get_one("a").unwrap().value().unwrap(),
961 Scalar::Float(1000.0)
962 );
963 assert_eq!(
964 *root.get_one("b").unwrap().value().unwrap(),
965 Scalar::Float(-0.5)
966 );
967 assert_eq!(
968 *root.get_one("c").unwrap().value().unwrap(),
969 Scalar::Float(0.02)
970 );
971 }
972
973 #[test]
974 fn error_position_after_multibyte_content_reports_correct_line() {
975 let err = read_json("{\"s\": \"café \u{1F600}\"}\n@").unwrap_err();
982 assert!(
983 matches!(&err, OmnistError::Parse(e) if e.line == 2),
984 "got {err:?}"
985 );
986 }
987
988 #[test]
989 fn empty_object_and_array_read() {
990 let doc = read_json(r#"{"a": {}}"#).unwrap();
991 assert!(doc.root().get_one("a").unwrap().edges().unwrap().is_empty());
992 }
993
994 #[test]
995 fn empty_input_is_unexpected_end_of_input_error() {
996 let err = read_json("").unwrap_err();
997 assert!(
998 matches!(&err, OmnistError::Parse(e) if e.message.contains("unexpected end of input")),
999 "got {err:?}"
1000 )
1001 }
1002
1003 #[test]
1004 fn unrecognized_character_is_a_parse_error() {
1005 let err = read_json("@").unwrap_err();
1006 assert!(
1007 matches!(&err, OmnistError::Parse(e) if e.message.contains("unexpected character")),
1008 "got {err:?}"
1009 )
1010 }
1011
1012 #[test]
1013 fn a_bareword_that_only_partially_matches_a_keyword_is_unexpected_character() {
1014 let err = read_json("tx").unwrap_err();
1017 assert!(
1018 matches!(&err, OmnistError::Parse(e) if e.message.contains("unexpected character")),
1019 "got {err:?}"
1020 )
1021 }
1022
1023 #[test]
1024 fn error_position_reports_the_line_after_a_newline() {
1025 let err = read_json("{\n \"a\": @\n}").unwrap_err();
1026 assert!(
1027 matches!(&err, OmnistError::Parse(e) if e.line == 2),
1028 "got {err:?}"
1029 );
1030 }
1031
1032 #[test]
1033 fn object_missing_colon_is_a_parse_error() {
1034 let err = read_json(r#"{"a" 1}"#).unwrap_err();
1035 assert!(
1036 matches!(&err, OmnistError::Parse(e) if e.message.contains("expected ':'")),
1037 "got {err:?}"
1038 )
1039 }
1040
1041 #[test]
1042 fn object_missing_key_is_a_parse_error() {
1043 let err = read_json(r#"{1: 2}"#).unwrap_err();
1044 assert!(
1045 matches!(&err, OmnistError::Parse(e) if e.message.contains("expected string key")),
1046 "got {err:?}"
1047 )
1048 }
1049
1050 #[test]
1051 fn object_missing_comma_or_brace_is_a_parse_error() {
1052 let err = read_json(r#"{"a": 1 "b": 2}"#).unwrap_err();
1053 assert!(
1054 matches!(&err, OmnistError::Parse(e) if e.message.contains("expected ',' or '}'")),
1055 "got {err:?}"
1056 )
1057 }
1058
1059 #[test]
1060 fn array_missing_comma_or_bracket_is_a_parse_error() {
1061 let err = read_json(r#"{"a": [1 2]}"#).unwrap_err();
1062 assert!(
1063 matches!(&err, OmnistError::Parse(e) if e.message.contains("expected ',' or ']'")),
1064 "got {err:?}"
1065 )
1066 }
1067
1068 #[test]
1069 fn unterminated_string_is_a_parse_error() {
1070 let err = read_json(r#"{"a": "hi}"#).unwrap_err();
1071 assert!(
1072 matches!(&err, OmnistError::Parse(e) if e.message.contains("unterminated string")),
1073 "got {err:?}"
1074 )
1075 }
1076
1077 #[test]
1078 fn control_character_in_string_is_a_parse_error() {
1079 let err = read_json("{\"a\": \"x\u{0007}y\"}").unwrap_err();
1080 assert!(
1081 matches!(&err, OmnistError::Parse(e) if e.message.contains("control character")),
1082 "got {err:?}"
1083 )
1084 }
1085
1086 #[test]
1087 fn every_short_escape_reads_its_control_character() {
1088 let doc = read_json(r#"{"a": "\/\b\f"}"#).unwrap();
1089 let v = doc.root().get_one("a").unwrap();
1090 assert_eq!(
1091 *v.value().unwrap(),
1092 Scalar::Str("/\u{08}\u{0c}".to_string())
1093 );
1094 }
1095
1096 #[test]
1097 fn invalid_escape_character_is_a_parse_error() {
1098 let err = read_json(r#"{"a": "\q"}"#).unwrap_err();
1099 assert!(
1100 matches!(&err, OmnistError::Parse(e) if e.message.contains("invalid escape")),
1101 "got {err:?}"
1102 )
1103 }
1104
1105 #[test]
1106 fn unpaired_high_surrogate_is_a_parse_error() {
1107 let err = read_json(r#"{"a": "\ud800x"}"#).unwrap_err();
1108 assert!(
1109 matches!(&err, OmnistError::Parse(e) if e.message.contains("unpaired high surrogate")),
1110 "got {err:?}"
1111 )
1112 }
1113
1114 #[test]
1115 fn high_surrogate_followed_by_non_low_surrogate_escape_is_a_parse_error() {
1116 const BSL: char = '\u{5c}';
1125 let input = format!("{{\"a\": \"{BSL}ud800{BSL}u0041\"}}");
1126 let err = read_json(&input).unwrap_err();
1127 assert!(
1128 matches!(&err, OmnistError::Parse(e) if e.message.contains("invalid low surrogate")),
1129 "got {err:?}"
1130 )
1131 }
1132
1133 #[test]
1134 fn unpaired_low_surrogate_is_a_parse_error() {
1135 let err = read_json(r#"{"a": "\udc00"}"#).unwrap_err();
1136 assert!(
1137 matches!(&err, OmnistError::Parse(e) if e.message.contains("unpaired low surrogate")),
1138 "got {err:?}"
1139 )
1140 }
1141
1142 #[test]
1143 fn unterminated_unicode_escape_is_a_parse_error() {
1144 let err = read_json(r#"{"a": "\u12"#).unwrap_err();
1145 assert!(
1146 matches!(&err, OmnistError::Parse(e) if e.message.contains("unterminated unicode escape")),
1147 "got {err:?}"
1148 )
1149 }
1150
1151 #[test]
1152 fn invalid_hex_digit_in_unicode_escape_is_a_parse_error() {
1153 let err = read_json(r#"{"a": "\u12zz"}"#).unwrap_err();
1154 assert!(
1155 matches!(&err, OmnistError::Parse(e) if e.message.contains("invalid hex digit")),
1156 "got {err:?}"
1157 )
1158 }
1159
1160 #[test]
1161 fn invalid_number_literal_is_a_parse_error() {
1162 let err = read_json(r#"{"a": -x}"#).unwrap_err();
1163 assert!(
1164 matches!(&err, OmnistError::Parse(e) if e.message.contains("invalid number literal")),
1165 "got {err:?}"
1166 )
1167 }
1168
1169 fn doc_of(v: Value) -> Doc {
1172 Doc::of(&v).unwrap()
1173 }
1174
1175 #[test]
1176 fn round_trips_every_scalar_kind() {
1177 let v = obj(vec![
1178 ("null", Value::Null),
1179 ("bool", Value::Bool(true)),
1180 ("int", Value::Int((42).into())),
1181 ("float", Value::Float(1.5)),
1182 ("str", Value::Str("hi".to_string())),
1183 ]);
1184 let doc = doc_of(v);
1185 let text = write_json(&doc, None, false, None).unwrap();
1186 let back = read_json(&text).unwrap();
1187 assert!(doc.eq_doc(&back));
1188 }
1189
1190 #[test]
1191 fn round_trips_integral_float_at_and_above_1e17_boundary_issue_46() {
1192 for x in [1.0e17, 1.0e18, -1.23e17, 9.9e16_f64] {
1198 let doc = doc_of(obj(vec![("a", Value::Float(x))]));
1199 let text = write_json(&doc, None, false, None).unwrap();
1200 let back = read_json(&text).unwrap();
1201 assert_eq!(
1202 *back.root().get_one("a").unwrap().value().unwrap(),
1203 Scalar::Float(x),
1204 "x={x} text={text}"
1205 );
1206 }
1207 }
1208
1209 #[test]
1210 fn round_trips_temporal_like_strings_since_scalar_has_no_temporal_type() {
1211 let v = obj(vec![("d", Value::Str("2024-01-15".to_string()))]);
1215 let doc = doc_of(v);
1216 let mut rep = WriteReport::new();
1217 let text = write_json(&doc, None, false, Some(&mut rep)).unwrap();
1218 assert!(rep.is_empty());
1219 let back = read_json(&text).unwrap();
1220 assert!(doc.eq_doc(&back));
1221 }
1222
1223 #[test]
1224 fn writes_repeated_labels_as_a_json_array() {
1225 let doc = doc_of(obj(vec![(
1226 "m",
1227 Value::Array(vec![Value::Int((1).into()), Value::Int((2).into())]),
1228 )]));
1229 let text = write_json(&doc, None, false, None).unwrap();
1230 assert_eq!(text, r#"{"m": [1, 2]}"#);
1231 }
1232
1233 #[test]
1234 fn writes_compact_with_comma_space_separators() {
1235 let doc = doc_of(obj(vec![
1236 ("a", Value::Int((1).into())),
1237 ("b", Value::Int((2).into())),
1238 ]));
1239 let text = write_json(&doc, None, false, None).unwrap();
1240 assert_eq!(text, r#"{"a": 1, "b": 2}"#);
1241 }
1242
1243 #[test]
1244 fn writes_indented_multiline() {
1245 let doc = doc_of(obj(vec![("a", Value::Int((1).into()))]));
1246 let text = write_json(&doc, Some(2), false, None).unwrap();
1247 assert_eq!(text, "{\n \"a\": 1\n}");
1248 }
1249
1250 #[test]
1251 fn writes_empty_object_compactly() {
1252 let doc = doc_of(obj(vec![("o", Value::Object(IndexMap::new()))]));
1253 let text = write_json(&doc, Some(2), false, None).unwrap();
1254 assert!(text.contains("\"o\": {}"));
1255 }
1256
1257 #[test]
1258 fn an_empty_array_value_produces_no_edge_at_all() {
1259 let doc = doc_of(obj(vec![
1264 ("a", Value::Array(vec![])),
1265 ("b", Value::Int((1).into())),
1266 ]));
1267 let text = write_json(&doc, None, false, None).unwrap();
1268 assert_eq!(text, r#"{"b": 1}"#);
1269 }
1270
1271 #[test]
1279 fn write_fails_unconditionally_on_nan_and_infinity_lenient() {
1280 let doc = doc_of(obj(vec![
1281 ("a", Value::Float(f64::NAN)),
1282 ("b", Value::Float(f64::INFINITY)),
1283 ]));
1284 let mut rep = WriteReport::new();
1285 let err = write_json(&doc, None, false, Some(&mut rep)).unwrap_err();
1286 assert!(err.to_string().contains("write.unsupported-value"));
1287 assert!(err.to_string().contains("$.a"));
1288 assert!(rep.is_empty());
1292 assert!(err.report().is_none());
1293 }
1294
1295 #[test]
1299 fn write_fails_unconditionally_on_nan_strict() {
1300 let doc = doc_of(obj(vec![("a", Value::Float(f64::NAN))]));
1301 let err = write_json(&doc, None, true, None).unwrap_err();
1302 assert!(err.to_string().contains("write.unsupported-value"));
1303 assert!(err.report().is_none());
1304 }
1305
1306 #[test]
1307 fn strict_write_with_no_adjustments_succeeds() {
1308 let doc = doc_of(obj(vec![("a", Value::Int((1).into()))]));
1309 let text = write_json(&doc, None, true, None).unwrap();
1310 assert_eq!(text, r#"{"a": 1}"#);
1311 }
1312
1313 #[test]
1314 fn check_json_reports_without_producing_output() {
1315 let doc = doc_of(obj(vec![("a", Value::Float(f64::INFINITY))]));
1316 let rep = check_json(&doc);
1317 assert_eq!(rep.len(), 1);
1318 assert_eq!(rep.adjustments()[0].path, "$.a");
1319 assert_eq!(rep.adjustments()[0].code, "write.unsupported-value");
1323 }
1324
1325 #[test]
1326 fn writes_string_escapes() {
1327 let doc = doc_of(obj(vec![("s", Value::Str("a\n\"\\\tb".to_string()))]));
1328 let text = write_json(&doc, None, false, None).unwrap();
1329 assert_eq!(text, r#"{"s": "a\n\"\\\tb"}"#);
1330 }
1331
1332 #[test]
1333 fn writes_unicode_without_escaping_non_ascii() {
1334 let doc = doc_of(obj(vec![("s", Value::Str("caf\u{e9}".to_string()))]));
1336 let text = write_json(&doc, None, false, None).unwrap();
1337 assert_eq!(text, "{\"s\": \"caf\u{e9}\"}");
1338 }
1339
1340 #[test]
1341 fn writes_float_with_trailing_dot_zero_for_integral_values() {
1342 let doc = doc_of(obj(vec![("f", Value::Float(2.0))]));
1343 let text = write_json(&doc, None, false, None).unwrap();
1344 assert_eq!(text, r#"{"f": 2.0}"#);
1345 }
1346
1347 #[test]
1352 fn write_fails_unconditionally_on_negative_infinity() {
1353 let doc = doc_of(obj(vec![("f", Value::Float(f64::NEG_INFINITY))]));
1354 let err = write_json(&doc, None, true, None).unwrap_err();
1355 assert!(err.to_string().contains("write.unsupported-value"));
1356 assert!(err.report().is_none());
1357 }
1358
1359 #[test]
1360 fn write_float_directly_covers_every_branch() {
1361 let mut out = String::new();
1362 write_float(f64::NAN, &mut out);
1363 assert_eq!(out, "NaN");
1364 out.clear();
1365 write_float(f64::INFINITY, &mut out);
1366 assert_eq!(out, "Infinity");
1367 out.clear();
1368 write_float(f64::NEG_INFINITY, &mut out);
1369 assert_eq!(out, "-Infinity");
1370 out.clear();
1371 write_float(1.5, &mut out);
1372 assert_eq!(out, "1.5");
1373 }
1374
1375 #[test]
1376 fn writes_carriage_return_and_control_character_escapes() {
1377 let doc = doc_of(obj(vec![(
1378 "s",
1379 Value::Str("a\rb\u{08}c\u{0c}d\u{01}".to_string()),
1380 )]));
1381 let text = write_json(&doc, None, false, None).unwrap();
1382 const BS: char = '\u{5c}';
1383 let expected = format!("{{\"s\": \"a{BS}rb{BS}bc{BS}fd{BS}u0001\"}}");
1384 assert_eq!(text, expected);
1385 }
1386
1387 #[test]
1388 fn deeply_nested_document_write_reuses_doc_construction_depth_guard() {
1389 let mut v = Value::Int((0).into());
1393 for _ in 0..=crate::document::MAX_DEPTH {
1394 v = obj(vec![("a", v)]);
1395 }
1396 assert!(Doc::of(&v).is_err());
1397 }
1398
1399 #[test]
1400 fn round_trip_via_live_python_equivalent_scalars() {
1401 let doc = doc_of(obj(vec![
1406 ("a", Value::Int((1).into())),
1407 ("b", Value::Str("x".to_string())),
1408 (
1409 "c",
1410 Value::Array(vec![
1411 Value::Int((1).into()),
1412 Value::Int((2).into()),
1413 Value::Int((3).into()),
1414 ]),
1415 ),
1416 ]));
1417 let text = write_json(&doc, None, false, None).unwrap();
1418 assert_eq!(text, r#"{"a": 1, "b": "x", "c": [1, 2, 3]}"#);
1419 }
1420
1421 #[test]
1422 fn test_deeply_nested_json_depth_limit() {
1423 let nested = "[".repeat(50_000) + &"]".repeat(50_000);
1424 let err = read_json(&nested).unwrap_err();
1425 assert!(err.to_string().contains("maximum depth"));
1426 }
1427
1428 fn interleaved_doc() -> Doc {
1436 Doc::from_raw(crate::document::RawNode::Edges(vec![
1438 (
1439 "m".to_string(),
1440 crate::document::RawNode::Leaf(Scalar::Str("A".to_string())),
1441 ),
1442 (
1443 "x".to_string(),
1444 crate::document::RawNode::Leaf(Scalar::Str("X".to_string())),
1445 ),
1446 (
1447 "m".to_string(),
1448 crate::document::RawNode::Leaf(Scalar::Str("B".to_string())),
1449 ),
1450 ]))
1451 .unwrap()
1452 }
1453
1454 fn contiguous_repeat_doc() -> Doc {
1455 Doc::from_raw(crate::document::RawNode::Edges(vec![
1457 (
1458 "m".to_string(),
1459 crate::document::RawNode::Leaf(Scalar::Str("A".to_string())),
1460 ),
1461 (
1462 "m".to_string(),
1463 crate::document::RawNode::Leaf(Scalar::Str("B".to_string())),
1464 ),
1465 (
1466 "x".to_string(),
1467 crate::document::RawNode::Leaf(Scalar::Str("X".to_string())),
1468 ),
1469 ]))
1470 .unwrap()
1471 }
1472
1473 #[test]
1474 fn reports_interleaving_lost_on_write() {
1475 let doc = interleaved_doc();
1476 let mut report = crate::report::WriteReport::new();
1477 let text = write_json(&doc, None, false, Some(&mut report)).unwrap();
1478 assert_eq!(text, r#"{"m": ["A", "B"], "x": "X"}"#);
1479 let adjustments = report.adjustments();
1480 assert_eq!(adjustments.len(), 1);
1481 assert_eq!(adjustments[0].path, "$");
1482 assert_eq!(adjustments[0].code, "format.interleaving-lost");
1483 assert_eq!(adjustments[0].severity, Severity::Warning);
1484 }
1485
1486 #[test]
1487 fn check_json_reports_interleaving_lost() {
1488 let rep = check_json(&interleaved_doc());
1489 assert_eq!(rep.adjustments().len(), 1);
1490 assert_eq!(rep.adjustments()[0].code, "format.interleaving-lost");
1491 }
1492
1493 #[test]
1494 fn contiguous_repeated_label_does_not_report_interleaving_lost() {
1495 let doc = contiguous_repeat_doc();
1496 let mut report = crate::report::WriteReport::new();
1497 let text = write_json(&doc, None, false, Some(&mut report)).unwrap();
1498 assert_eq!(text, r#"{"m": ["A", "B"], "x": "X"}"#);
1499 assert!(report.is_empty());
1500 assert!(check_json(&doc).is_empty());
1501 }
1502}