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