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