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