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