Skip to main content

reliakit_json/
lib.rs

1//! Strict, bounded, and deterministic JSON for reliability-sensitive Rust.
2//!
3//! `reliakit-json` is built for systems that process **untrusted** JSON or need
4//! **predictable** output: it parses a strict subset of [RFC 8259], rejects
5//! duplicate object keys, enforces explicit [resource limits](JsonLimits),
6//! preserves number precision, reports errors with location and path, and
7//! serializes deterministically. It has no external dependencies, forbids
8//! unsafe code, and supports `no_std` (with `alloc`).
9//!
10//! It deliberately does **not** provide derive macros, schema validation,
11//! JSON5, comments, trailing commas, lenient parsing, or SIMD throughput.
12//!
13//! # Example
14//!
15//! ```
16//! use reliakit_json::{parse_str, to_compact_string};
17//!
18//! let value = parse_str(r#"{"name":"reliakit","ok":true}"#).unwrap();
19//! assert_eq!(value.as_object().unwrap().get("name").unwrap().as_str(), Some("reliakit"));
20//!
21//! // Serialization is deterministic and preserves member order.
22//! assert_eq!(to_compact_string(&value), r#"{"name":"reliakit","ok":true}"#);
23//!
24//! // Strict by default: duplicate keys are rejected, not silently resolved.
25//! assert!(parse_str(r#"{"a":1,"a":2}"#).is_err());
26//! ```
27//!
28//! # Limits
29//!
30//! [`parse`] applies conservative [`JsonLimits`] by default. Use
31//! [`parse_with_limits`] to choose a profile or tune individual limits:
32//!
33//! ```
34//! use reliakit_json::{parse_with_limits, JsonLimits};
35//!
36//! let limits = JsonLimits::conservative().with_max_depth(8);
37//! assert!(parse_with_limits(b"[[[[[[[[[[1]]]]]]]]]]", limits).is_err());
38//! ```
39//!
40//! [RFC 8259]: https://www.rfc-editor.org/rfc/rfc8259
41
42#![cfg_attr(not(feature = "std"), no_std)]
43#![forbid(unsafe_code)]
44#![warn(missing_docs)]
45
46extern crate alloc;
47
48#[cfg(feature = "canonical")]
49mod canonical;
50mod error;
51mod limits;
52mod number;
53mod parse;
54mod value;
55mod write;
56
57#[cfg(feature = "canonical")]
58pub use canonical::{to_canonical_string, to_canonical_vec};
59pub use error::{
60    JsonError, JsonErrorKind, JsonLimitKind, JsonNumberError, JsonPath, JsonPathSegment,
61};
62pub use limits::JsonLimits;
63pub use number::JsonNumber;
64pub use parse::{parse, parse_str, parse_with_limits};
65pub use value::{JsonMember, JsonObject, JsonValue};
66pub use write::{to_compact_string, to_compact_vec};
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use alloc::string::{String, ToString};
72
73    fn parse_ok(input: &str) -> JsonValue {
74        parse_str(input).expect("should parse")
75    }
76
77    fn kind(input: &str) -> JsonErrorKind {
78        parse_str(input).expect_err("should fail").kind().clone()
79    }
80
81    // ---- scalars ----------------------------------------------------------
82
83    #[test]
84    fn parses_scalars() {
85        assert_eq!(parse_ok("null"), JsonValue::Null);
86        assert_eq!(parse_ok("true"), JsonValue::Bool(true));
87        assert_eq!(parse_ok("false"), JsonValue::Bool(false));
88        assert_eq!(parse_ok("\"hi\"").as_str(), Some("hi"));
89        assert_eq!(parse_ok("42").as_number().unwrap().to_i64().unwrap(), 42);
90    }
91
92    #[test]
93    fn whitespace_is_allowed_around_values() {
94        assert_eq!(parse_ok("  \t\r\n 7 \n").as_number().unwrap().as_str(), "7");
95    }
96
97    #[test]
98    fn only_json_whitespace_is_accepted() {
99        // A vertical tab (U+000B) is not JSON whitespace.
100        assert_eq!(kind("\u{0B}1"), JsonErrorKind::UnexpectedByte);
101    }
102
103    // ---- structure --------------------------------------------------------
104
105    #[test]
106    fn parses_object_and_array() {
107        let value = parse_ok(r#"{"a":[1,2,3],"b":{"c":null}}"#);
108        let obj = value.as_object().unwrap();
109        assert_eq!(obj.len(), 2);
110        assert_eq!(obj.get("a").unwrap().as_array().unwrap().len(), 3);
111        assert!(obj
112            .get("b")
113            .unwrap()
114            .as_object()
115            .unwrap()
116            .get("c")
117            .unwrap()
118            .is_null());
119    }
120
121    #[test]
122    fn empty_containers() {
123        assert_eq!(parse_ok("[]").as_array().unwrap().len(), 0);
124        assert_eq!(parse_ok("{}").as_object().unwrap().len(), 0);
125    }
126
127    // ---- required rejections ---------------------------------------------
128
129    #[test]
130    fn rejects_trailing_data() {
131        assert_eq!(kind("1 2"), JsonErrorKind::TrailingData);
132        assert_eq!(kind("{} x"), JsonErrorKind::TrailingData);
133    }
134
135    #[test]
136    fn rejects_comments_and_trailing_commas() {
137        assert_eq!(kind("1 // c"), JsonErrorKind::TrailingData);
138        assert_eq!(kind("[1,]"), JsonErrorKind::UnexpectedByte);
139        assert_eq!(kind(r#"{"a":1,}"#), JsonErrorKind::UnexpectedByte);
140    }
141
142    #[test]
143    fn rejects_bad_numbers() {
144        for bad in ["01", "1.", "-", "1e", "1e+", "00", "1.2.3"] {
145            assert_eq!(kind(bad), JsonErrorKind::InvalidNumber, "input {bad:?}");
146        }
147        // Also rejected, with their own correct kinds (no valid value starts
148        // with '.' or '+'; "0x1" parses "0" then chokes on the trailing "x1").
149        assert_eq!(kind(".5"), JsonErrorKind::UnexpectedByte);
150        assert_eq!(kind("+1"), JsonErrorKind::UnexpectedByte);
151        assert_eq!(kind("0x1"), JsonErrorKind::TrailingData);
152    }
153
154    #[test]
155    fn rejects_nan_and_infinity() {
156        assert_eq!(kind("NaN"), JsonErrorKind::UnexpectedByte);
157        assert_eq!(kind("Infinity"), JsonErrorKind::UnexpectedByte);
158        assert_eq!(kind("-Infinity"), JsonErrorKind::InvalidNumber);
159    }
160
161    #[test]
162    fn rejects_unescaped_control_and_bad_escapes() {
163        assert_eq!(kind("\"\u{01}\""), JsonErrorKind::UnescapedControlCharacter);
164        assert_eq!(kind(r#""\x""#), JsonErrorKind::InvalidEscape);
165        assert_eq!(kind(r#""\u00""#), JsonErrorKind::InvalidUnicodeEscape);
166    }
167
168    #[test]
169    fn rejects_lone_surrogates() {
170        assert_eq!(kind(r#""\uD800""#), JsonErrorKind::LoneSurrogate);
171        assert_eq!(kind(r#""\uDC00""#), JsonErrorKind::LoneSurrogate);
172        assert_eq!(kind(r#""\uD800a""#), JsonErrorKind::LoneSurrogate);
173    }
174
175    #[test]
176    fn accepts_valid_surrogate_pair() {
177        assert_eq!(parse_ok(r#""𝄞""#).as_str(), Some("\u{1D11E}"));
178    }
179
180    #[test]
181    fn rejects_invalid_utf8_and_bom() {
182        assert_eq!(
183            parse(&[0xff]).unwrap_err().kind().clone(),
184            JsonErrorKind::InvalidUtf8
185        );
186        assert_eq!(
187            parse(&[0xEF, 0xBB, 0xBF, b'1']).unwrap_err().kind().clone(),
188            JsonErrorKind::InvalidUtf8
189        );
190    }
191
192    // ---- string semantics -------------------------------------------------
193
194    #[test]
195    fn escape_and_literal_decode_equally() {
196        assert_eq!(parse_ok(r#""a""#), parse_ok(r#""a""#));
197    }
198
199    #[test]
200    fn decodes_named_escapes() {
201        assert_eq!(
202            parse_ok(r#""\n\t\r\b\f\"\\\/""#).as_str(),
203            Some("\n\t\r\u{08}\u{0C}\"\\/")
204        );
205    }
206
207    // ---- duplicate keys ---------------------------------------------------
208
209    #[test]
210    fn rejects_duplicate_keys() {
211        assert_eq!(kind(r#"{"a":1,"a":2}"#), JsonErrorKind::DuplicateKey);
212    }
213
214    #[test]
215    fn duplicate_detection_is_after_escape_decoding() {
216        assert_eq!(
217            kind(r#"{"role":"user","role":"admin"}"#),
218            JsonErrorKind::DuplicateKey
219        );
220    }
221
222    // ---- limits -----------------------------------------------------------
223
224    #[test]
225    fn enforces_depth_limit() {
226        let limits = JsonLimits::new().with_max_depth(3);
227        assert!(parse_with_limits(b"[[[1]]]", limits).is_ok());
228        assert_eq!(
229            parse_with_limits(b"[[[[1]]]]", limits)
230                .unwrap_err()
231                .kind()
232                .clone(),
233            JsonErrorKind::LimitExceeded(JsonLimitKind::Depth)
234        );
235    }
236
237    #[test]
238    fn enforces_count_limits() {
239        let limits = JsonLimits::new();
240        let limits = JsonLimits {
241            max_array_items: 2,
242            max_object_members: 2,
243            max_total_nodes: 100,
244            ..limits
245        };
246        assert_eq!(
247            parse_with_limits(b"[1,2,3]", limits)
248                .unwrap_err()
249                .kind()
250                .clone(),
251            JsonErrorKind::LimitExceeded(JsonLimitKind::ArrayItems)
252        );
253        assert_eq!(
254            parse_with_limits(br#"{"a":1,"b":2,"c":3}"#, limits)
255                .unwrap_err()
256                .kind()
257                .clone(),
258            JsonErrorKind::LimitExceeded(JsonLimitKind::ObjectMembers)
259        );
260    }
261
262    #[test]
263    fn enforces_total_nodes_and_input_bytes() {
264        let nodes = JsonLimits::new().with_max_total_nodes(2);
265        assert_eq!(
266            parse_with_limits(b"[1,2]", nodes)
267                .unwrap_err()
268                .kind()
269                .clone(),
270            JsonErrorKind::LimitExceeded(JsonLimitKind::TotalNodes)
271        );
272        let bytes = JsonLimits::new().with_max_input_bytes(2);
273        assert_eq!(
274            parse_with_limits(b"[1]", bytes).unwrap_err().kind().clone(),
275            JsonErrorKind::LimitExceeded(JsonLimitKind::InputBytes)
276        );
277    }
278
279    #[test]
280    fn enforces_string_and_number_byte_limits() {
281        let s = JsonLimits {
282            max_string_bytes: 3,
283            ..JsonLimits::new()
284        };
285        assert_eq!(
286            parse_with_limits(br#""abcd""#, s)
287                .unwrap_err()
288                .kind()
289                .clone(),
290            JsonErrorKind::LimitExceeded(JsonLimitKind::StringBytes)
291        );
292        let n = JsonLimits {
293            max_number_bytes: 2,
294            ..JsonLimits::new()
295        };
296        assert_eq!(
297            parse_with_limits(b"12345", n).unwrap_err().kind().clone(),
298            JsonErrorKind::LimitExceeded(JsonLimitKind::NumberBytes)
299        );
300    }
301
302    // ---- numbers ----------------------------------------------------------
303
304    #[test]
305    fn number_conversions() {
306        assert_eq!(parse_ok("-7").as_number().unwrap().to_i64().unwrap(), -7);
307        assert_eq!(parse_ok("7").as_number().unwrap().to_u64().unwrap(), 7);
308        assert!((parse_ok("1.5").as_number().unwrap().to_f64().unwrap() - 1.5).abs() < 1e-12);
309        assert_eq!(
310            parse_ok("1.5").as_number().unwrap().to_i64(),
311            Err(JsonNumberError::NotAnInteger)
312        );
313        assert_eq!(
314            parse_ok("99999999999999999999999")
315                .as_number()
316                .unwrap()
317                .to_i64(),
318            Err(JsonNumberError::OutOfRange)
319        );
320        assert_eq!(
321            parse_ok("1e400").as_number().unwrap().to_f64(),
322            Err(JsonNumberError::NotFinite)
323        );
324    }
325
326    #[test]
327    fn number_preserves_representation() {
328        assert_eq!(parse_ok("1.0").as_number().unwrap().as_str(), "1.0");
329        assert_ne!(parse_ok("1.0"), parse_ok("1")); // structural equality
330    }
331
332    #[test]
333    fn json_number_from_f64() {
334        assert_eq!(JsonNumber::try_from_f64(1.5).unwrap().as_str(), "1.5");
335        assert_eq!(
336            JsonNumber::try_from_f64(f64::NAN),
337            Err(JsonNumberError::NotFinite)
338        );
339        assert_eq!(
340            JsonNumber::try_from_f64(f64::INFINITY),
341            Err(JsonNumberError::NotFinite)
342        );
343        assert_eq!(JsonNumber::new("01"), Err(JsonNumberError::InvalidNumber));
344    }
345
346    // ---- errors -----------------------------------------------------------
347
348    #[test]
349    fn error_reports_location_and_path() {
350        let err = parse_str("  @").unwrap_err();
351        assert_eq!(err.kind().clone(), JsonErrorKind::UnexpectedByte);
352        assert_eq!(err.offset(), 2);
353        assert_eq!(err.line(), 1);
354        assert_eq!(err.column(), 3);
355
356        let err = parse_str(r#"{"users":[{"name":1},{"name":}]}"#).unwrap_err();
357        let path = err.path().unwrap().to_string();
358        assert_eq!(path, "$.users[1].name");
359    }
360
361    // ---- serialization ----------------------------------------------------
362
363    #[test]
364    fn compact_roundtrip_and_golden_bytes() {
365        let value = parse_ok(r#"{"a":1,"b":true,"c":[null,"x"]}"#);
366        assert_eq!(
367            to_compact_vec(&value),
368            br#"{"a":1,"b":true,"c":[null,"x"]}"#
369        );
370        // Roundtrip: serialize, reparse, equal value.
371        let again = parse_str(&to_compact_string(&value)).unwrap();
372        assert_eq!(value, again);
373    }
374
375    #[test]
376    fn writer_escapes_control_and_special_characters() {
377        let mut object = JsonObject::new();
378        object.insert(
379            String::from("k"),
380            JsonValue::String(String::from("a\nb\"c\\\u{01}")),
381        );
382        let value = JsonValue::Object(object);
383        assert_eq!(to_compact_string(&value), r#"{"k":"a\nb\"c\\\u0001"}"#);
384    }
385
386    #[test]
387    fn object_insert_replaces_in_place() {
388        let mut object = JsonObject::new();
389        assert!(object
390            .insert(String::from("a"), JsonValue::Bool(true))
391            .is_none());
392        let old = object.insert(String::from("a"), JsonValue::Bool(false));
393        assert_eq!(old, Some(JsonValue::Bool(true)));
394        assert_eq!(object.len(), 1);
395    }
396
397    #[test]
398    fn deeply_nested_within_limits_does_not_overflow() {
399        // Build input nested to the default limit and confirm bounded handling.
400        let depth = 64;
401        let mut s = String::new();
402        for _ in 0..depth {
403            s.push('[');
404        }
405        s.push('1');
406        for _ in 0..depth {
407            s.push(']');
408        }
409        // Default max_depth is 64, so depth 64 is at the edge; depth 65 fails.
410        let _ = parse_str(&s); // must not panic regardless of accept/reject
411        assert!(parse_with_limits(s.as_bytes(), JsonLimits::new().with_max_depth(64)).is_ok());
412    }
413
414    #[test]
415    fn arbitrary_bytes_never_panic() {
416        // Smoke test: a spread of odd inputs must each return Ok or Err, never panic.
417        for input in [
418            &b""[..],
419            b"   ",
420            b"{",
421            b"[",
422            b"\"",
423            b"\"\\",
424            b"\"\\u",
425            b"tru",
426            b"-",
427            b"[,]",
428            b"{,}",
429            b"\xff\xfe",
430            b"[[[",
431            b"}}}",
432            b"\"\\uD800\"",
433            b"1e",
434            b"{\"a\"}",
435        ] {
436            let _ = parse(input);
437        }
438    }
439
440    #[test]
441    fn json_test_suite_conformance() {
442        // Curated accept/reject cases in the spirit of nst/JSONTestSuite. The
443        // parser must accept every `y_` case and reject every `n_` case.
444        let must_accept: &[&[u8]] = &[
445            b"[]",
446            b"{}",
447            b"[1]",
448            b"[1,2,3]",
449            b"{\"a\":1}",
450            b"{\"a\":1,\"b\":2}",
451            b"[null,true,false]",
452            b"\"\\u0061\"",
453            b"\"\\uD834\\uDD1E\"", // valid surrogate pair (U+1D11E)
454            b"0",
455            b"-0",
456            b"123",
457            b"-123",
458            b"1.5",
459            b"1E10",
460            b"1e-10",
461            b"-1.2e+3",
462            b"  7  ",
463            b"\"abc\"",
464            b"true",
465            b"[[[[1]]]]",
466            b"{\"a\":{\"b\":[1,{\"c\":null}]}}",
467        ];
468        let must_reject: &[&[u8]] = &[
469            b"",
470            b"[1,]",
471            b"{\"a\":1,}",
472            b"[1 2]",
473            b"{\"a\" 1}",
474            b"{\"a\":1 \"b\":2}",
475            b"[1,,2]",
476            b"01",
477            b"1.",
478            b".1",
479            b"+1",
480            b"1e",
481            b"1e+",
482            b"0x1",
483            b"--1",
484            b"NaN",
485            b"Infinity",
486            b"[",
487            b"]",
488            b"{",
489            b"}",
490            b"\"",
491            b"\"\\x\"",
492            b"\"\\uZZZZ\"",
493            b"\"\x01\"", // raw control char in string
494            b"'single'",
495            b"1 1", // trailing data
496            b"tru",
497            b"nul",
498            b"\xEF\xBB\xBF1", // leading byte-order mark
499            b"\xff\xfe",      // invalid UTF-8
500            b"\"\\uD800\"",   // lone surrogate
501            b"/* comment */ 1",
502            b"{1:2}", // non-string key
503        ];
504        for input in must_accept {
505            assert!(parse(input).is_ok(), "should accept {input:?}");
506        }
507        for input in must_reject {
508            assert!(parse(input).is_err(), "should reject {input:?}");
509        }
510    }
511
512    #[test]
513    fn fuzz_parse_is_panic_free_and_roundtrips() {
514        // Deterministic in-test fuzzing: parsing arbitrary bytes never panics,
515        // and any value that parses survives a compact round-trip unchanged.
516        let mut state: u64 = 0xD1B5_4A32_D192_ED03;
517        let mut next = || {
518            state ^= state << 13;
519            state ^= state >> 7;
520            state ^= state << 17;
521            state
522        };
523        // Alphabet biased toward JSON tokens so successful parses are exercised.
524        let alphabet = b"{}[]\":,0123456789-+.eEtruefalsn \t\n\\/u";
525        let mut buf: Vec<u8> = Vec::new();
526        for _ in 0..40_000 {
527            buf.clear();
528            let len = (next() % 40) as usize;
529            for _ in 0..len {
530                let r = next();
531                let byte = if r & 7 == 0 {
532                    (r >> 8) as u8 // occasionally a fully arbitrary byte
533                } else {
534                    alphabet[((r >> 8) as usize) % alphabet.len()]
535                };
536                buf.push(byte);
537            }
538            if let Ok(value) = parse(&buf) {
539                let compact = to_compact_string(&value);
540                let reparsed = parse_str(&compact).expect("compact output must reparse");
541                assert_eq!(reparsed, value);
542                assert_eq!(to_compact_string(&reparsed), compact);
543
544                // Canonical (JCS) output must be idempotent when available.
545                #[cfg(feature = "canonical")]
546                if let Ok(canonical) = to_canonical_string(&value) {
547                    let again = parse_str(&canonical).expect("canonical output must reparse");
548                    assert_eq!(to_canonical_string(&again).unwrap(), canonical);
549                }
550            }
551        }
552    }
553
554    #[test]
555    fn value_accessors_return_inner_or_none() {
556        let v = parse_ok(r#"{"b":true,"n":7,"s":"x","a":[1],"nil":null}"#);
557        let o = v.as_object().expect("object");
558        assert!(o.get("nil").unwrap().is_null());
559        assert_eq!(o.get("b").unwrap().as_bool(), Some(true));
560        assert_eq!(o.get("s").unwrap().as_str(), Some("x"));
561        assert_eq!(o.get("n").unwrap().as_number().unwrap().as_str(), "7");
562        assert_eq!(o.get("a").unwrap().as_array().unwrap().len(), 1);
563
564        // Wrong-variant accessors return None.
565        let b = JsonValue::Bool(true);
566        assert!(!b.is_null());
567        assert_eq!(b.as_str(), None);
568        assert_eq!(b.as_number(), None);
569        assert_eq!(b.as_array(), None);
570        assert!(b.as_object().is_none());
571        assert_eq!(JsonValue::Null.as_bool(), None);
572    }
573
574    #[test]
575    fn object_insert_get_iter_and_len() {
576        let mut obj = JsonObject::new();
577        assert!(obj.is_empty());
578        assert_eq!(obj.len(), 0);
579        assert!(!obj.contains_key("k"));
580
581        assert_eq!(obj.insert("k".to_string(), JsonValue::Bool(false)), None);
582        assert!(obj.contains_key("k"));
583        assert_eq!(obj.len(), 1);
584
585        // Insert with an existing key replaces in place and returns the old value.
586        let old = obj.insert("k".to_string(), JsonValue::Bool(true));
587        assert_eq!(old, Some(JsonValue::Bool(false)));
588        assert_eq!(obj.len(), 1);
589        assert_eq!(obj.get("k"), Some(&JsonValue::Bool(true)));
590        assert_eq!(obj.get("missing"), None);
591
592        obj.insert("k2".to_string(), JsonValue::Null);
593        let members: Vec<&str> = obj.iter().map(|m| m.key()).collect();
594        assert_eq!(members, ["k", "k2"]);
595        assert_eq!(obj.iter().next().unwrap().value(), &JsonValue::Bool(true));
596
597        assert_eq!(JsonObject::default().len(), 0);
598    }
599
600    #[test]
601    fn number_conversions_cover_each_error() {
602        let int = JsonNumber::new("42").unwrap();
603        assert!(int.is_integer());
604        assert_eq!(int.to_i64(), Ok(42));
605        assert_eq!(int.to_u64(), Ok(42));
606        assert_eq!(int.to_f64(), Ok(42.0));
607
608        let neg = JsonNumber::new("-1").unwrap();
609        assert_eq!(neg.to_u64(), Err(JsonNumberError::OutOfRange));
610
611        let frac = JsonNumber::new("1.5").unwrap();
612        assert!(!frac.is_integer());
613        assert_eq!(frac.to_i64(), Err(JsonNumberError::NotAnInteger));
614        assert_eq!(frac.to_u64(), Err(JsonNumberError::NotAnInteger));
615        assert_eq!(frac.to_f64(), Ok(1.5));
616
617        let huge = JsonNumber::new("99999999999999999999").unwrap();
618        assert_eq!(huge.to_i64(), Err(JsonNumberError::OutOfRange));
619
620        let overflow = JsonNumber::new("1e400").unwrap();
621        assert_eq!(overflow.to_f64(), Err(JsonNumberError::NotFinite));
622
623        assert_eq!(JsonNumber::new("+1"), Err(JsonNumberError::InvalidNumber));
624        assert_eq!(JsonNumber::try_from_f64(2.5).unwrap().to_f64(), Ok(2.5));
625        assert_eq!(
626            JsonNumber::try_from_f64(f64::NAN),
627            Err(JsonNumberError::NotFinite)
628        );
629        assert_eq!(
630            JsonNumber::try_from_f64(f64::INFINITY),
631            Err(JsonNumberError::NotFinite)
632        );
633    }
634
635    #[test]
636    fn limits_profiles_and_builders() {
637        assert_eq!(JsonLimits::default(), JsonLimits::new());
638        assert!(JsonLimits::conservative().max_input_bytes < JsonLimits::new().max_input_bytes);
639        assert!(JsonLimits::permissive().max_input_bytes > JsonLimits::new().max_input_bytes);
640
641        let tuned = JsonLimits::new()
642            .with_max_depth(8)
643            .with_max_input_bytes(1024)
644            .with_max_string_bytes(16)
645            .with_max_total_nodes(32);
646        assert_eq!(tuned.max_depth, 8);
647        assert_eq!(tuned.max_input_bytes, 1024);
648        assert_eq!(tuned.max_string_bytes, 16);
649        assert_eq!(tuned.max_total_nodes, 32);
650    }
651
652    #[test]
653    fn error_display_covers_each_kind() {
654        // One representative input per simple kind, then check Display text.
655        let cases: &[(&str, &str)] = &[
656            ("", "unexpected end of input"),
657            ("@", "unexpected byte"),
658            ("\"a\\xb\"", "invalid escape sequence"),
659            ("\"\\uZZZZ\"", "invalid unicode escape"),
660            ("\"\\uD800\"", "unpaired UTF-16 surrogate"),
661            ("01", "invalid number"),
662            ("{\"a\":1,\"a\":2}", "duplicate object key"),
663            ("true false", "trailing data after JSON value"),
664        ];
665        for (input, expected) in cases {
666            let err = parse_str(input).unwrap_err();
667            assert!(
668                err.to_string().contains(expected),
669                "input {input:?} -> {err} (expected to contain {expected:?})"
670            );
671        }
672
673        // A control character inside a string.
674        let ctrl = parse(b"\"\x01\"").unwrap_err();
675        assert!(ctrl.to_string().contains("unescaped control character"));
676
677        // Invalid UTF-8 input.
678        let utf8 = parse(b"\xff").unwrap_err();
679        assert!(utf8.to_string().contains("invalid UTF-8"));
680    }
681
682    #[test]
683    fn error_accessors_and_limit_display_with_path() {
684        let limits = JsonLimits::new().with_max_depth(1);
685        let err = parse_with_limits(b"[[1]]", limits).unwrap_err();
686        assert_eq!(
687            err.kind(),
688            &JsonErrorKind::LimitExceeded(JsonLimitKind::Depth)
689        );
690        assert!(err.offset() >= 1);
691        assert_eq!(err.line(), 1);
692        assert!(err.column() >= 1);
693        let shown = err.to_string();
694        assert!(shown.contains("limit exceeded: nesting depth"));
695        assert!(shown.contains("path: $"));
696        assert_eq!(JsonLimitKind::Depth.as_str(), "nesting depth");
697    }
698
699    #[test]
700    fn path_display_formats_keys_and_indices() {
701        let path = JsonPath::from_segments(vec![
702            JsonPathSegment::Key("users".to_string()),
703            JsonPathSegment::Index(3),
704            JsonPathSegment::Key("email".to_string()),
705        ]);
706        assert_eq!(path.to_string(), "$.users[3].email");
707        assert_eq!(path.segments().len(), 3);
708        assert_eq!(JsonPath::default().to_string(), "$");
709    }
710
711    #[test]
712    fn number_error_display_is_distinct() {
713        assert_eq!(
714            JsonNumberError::OutOfRange.to_string(),
715            "number out of range for target type"
716        );
717        assert_eq!(
718            JsonNumberError::NotAnInteger.to_string(),
719            "number is not an integer"
720        );
721        assert_eq!(
722            JsonNumberError::NotFinite.to_string(),
723            "number is not finite"
724        );
725        assert_eq!(
726            JsonNumberError::InvalidNumber.to_string(),
727            "not a valid JSON number"
728        );
729    }
730
731    #[test]
732    fn writer_serializes_all_branches() {
733        let mut obj = JsonObject::new();
734        obj.insert("off".to_string(), JsonValue::Bool(false));
735        obj.insert(
736            "esc".to_string(),
737            // Named escapes plus a control char that needs a hex nibble a-f.
738            JsonValue::String("\u{08}\u{0C}\n\r\t\u{1F}".to_string()),
739        );
740        obj.insert(
741            "arr".to_string(),
742            JsonValue::Array(vec![JsonValue::Null, JsonValue::Bool(true)]),
743        );
744        let value = JsonValue::Object(obj);
745
746        let s = to_compact_string(&value);
747        // Round-trips back to the same value (exercises every escape branch,
748        // including a control char whose hex escape uses an a-f nibble).
749        assert_eq!(parse_str(&s).unwrap(), value);
750        assert_eq!(to_compact_vec(&value), s.clone().into_bytes());
751        assert!(s.starts_with("{\"off\":false,"));
752        assert!(s.ends_with(",\"arr\":[null,true]}"));
753    }
754
755    #[cfg(feature = "std")]
756    #[test]
757    fn errors_implement_std_error() {
758        fn assert_error<E: std::error::Error>(_: &E) {}
759        let parse_err = parse_str("").unwrap_err();
760        assert_error(&parse_err);
761        let num_err = JsonNumber::new("1.5").unwrap().to_i64().unwrap_err();
762        assert_error(&num_err);
763    }
764}