Skip to main content

rustev_contract/
bounded.rs

1//! Bounded JSON: resource limits checked on raw bytes before typed parsing.
2//!
3//! [`scan`] validates that a byte slice is exactly one RFC 8259 JSON value and
4//! that it stays within every [`ParseLimits`] bound, without recursion and
5//! without building any value. [`parse_bounded`] runs typed deserialization
6//! only after the scan accepts.
7//!
8//! Reading bytes from a socket, file or other transport is the caller's
9//! responsibility: this module judges a slice already in memory, so the
10//! transport must bound its own buffering (for example at `max_bytes`).
11
12use std::collections::HashSet;
13use std::fmt;
14
15/// Resource bounds for one JSON document. There is no default: every caller
16/// states its limits.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct ParseLimits {
19    /// Maximum total input length in bytes, checked before any content is read.
20    pub max_bytes: usize,
21    /// Maximum container nesting; a top-level array or object is depth 1.
22    pub max_depth: usize,
23    /// Maximum raw (still escaped) byte length between a string's quotes.
24    /// Applies to object keys and string values alike.
25    pub max_string_bytes: usize,
26    /// Maximum members of any one array or object.
27    pub max_collection_len: usize,
28    /// Maximum values in the document. Every scalar, array and object counts
29    /// one; object keys do not count.
30    pub max_total_values: usize,
31    /// Maximum raw byte length of a number literal.
32    pub max_number_bytes: usize,
33    /// When false, a number with a fraction or exponent part is refused.
34    pub allow_fractional_numbers: bool,
35}
36
37/// What an accepted scan observed.
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct ScanStats {
40    /// Input length in bytes.
41    pub bytes: usize,
42    /// Deepest container nesting reached (0 for a top-level scalar).
43    pub max_depth_seen: usize,
44    /// Values counted under [`ParseLimits::max_total_values`].
45    pub values: usize,
46}
47
48/// Why a scan refused its input. Offsets are byte offsets into the input.
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub enum BoundError {
51    /// The input is longer than `max_bytes`. Reported before reading content.
52    TooLarge { limit: usize, actual: usize },
53    /// A container would nest deeper than `max_depth`.
54    TooDeep { limit: usize, offset: usize },
55    /// A string's raw length exceeds `max_string_bytes`.
56    StringTooLong { limit: usize, offset: usize },
57    /// An array or object has more than `max_collection_len` members.
58    CollectionTooLong { limit: usize, offset: usize },
59    /// The document has more than `max_total_values` values.
60    TooManyValues { limit: usize, offset: usize },
61    /// A number literal is longer than `max_number_bytes`.
62    NumberTooLong { limit: usize, offset: usize },
63    /// A fraction or exponent where `allow_fractional_numbers` is false.
64    FractionalNumber { offset: usize },
65    /// A key repeated within one object, compared after decoding escapes.
66    /// `key` is truncated to 64 characters.
67    DuplicateKey { key: String, offset: usize },
68    /// The input is not JSON at `offset`; `expected` names what was expected.
69    Syntax {
70        offset: usize,
71        expected: &'static str,
72    },
73    /// A string contains bytes that are not valid UTF-8.
74    InvalidUtf8 { offset: usize },
75    /// Non-whitespace follows the single top-level value.
76    TrailingData { offset: usize },
77    /// The input is empty or whitespace only.
78    Empty,
79}
80
81impl fmt::Display for BoundError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        use BoundError::*;
84        match self {
85            TooLarge { limit, actual } => {
86                write!(f, "input is {actual} bytes, limit {limit}")
87            }
88            TooDeep { limit, offset } => {
89                write!(f, "nesting exceeds depth {limit} at byte {offset}")
90            }
91            StringTooLong { limit, offset } => {
92                write!(f, "string exceeds {limit} bytes at byte {offset}")
93            }
94            CollectionTooLong { limit, offset } => {
95                write!(f, "collection exceeds {limit} members at byte {offset}")
96            }
97            TooManyValues { limit, offset } => {
98                write!(f, "document exceeds {limit} values at byte {offset}")
99            }
100            NumberTooLong { limit, offset } => {
101                write!(f, "number exceeds {limit} bytes at byte {offset}")
102            }
103            FractionalNumber { offset } => {
104                write!(f, "fractional number not allowed at byte {offset}")
105            }
106            DuplicateKey { key, offset } => {
107                write!(f, "duplicate key {key:?} at byte {offset}")
108            }
109            Syntax { offset, expected } => {
110                write!(f, "expected {expected} at byte {offset}")
111            }
112            InvalidUtf8 { offset } => write!(f, "invalid UTF-8 at byte {offset}"),
113            TrailingData { offset } => write!(f, "trailing data at byte {offset}"),
114            Empty => write!(f, "empty input"),
115        }
116    }
117}
118
119impl std::error::Error for BoundError {}
120
121/// Why [`parse_bounded`] produced no value.
122#[derive(Clone, Debug, PartialEq, Eq)]
123pub enum ParseError {
124    /// The bounded scan refused the input; no typed value was built.
125    Bound(BoundError),
126    /// The input was within bounds but does not match the target type
127    /// (including unknown fields where the type denies them).
128    Schema { message: String },
129}
130
131impl fmt::Display for ParseError {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        match self {
134            ParseError::Bound(e) => write!(f, "bounds: {e}"),
135            ParseError::Schema { message } => write!(f, "schema: {message}"),
136        }
137    }
138}
139
140impl std::error::Error for ParseError {
141    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
142        match self {
143            ParseError::Bound(e) => Some(e),
144            ParseError::Schema { .. } => None,
145        }
146    }
147}
148
149impl From<BoundError> for ParseError {
150    fn from(e: BoundError) -> Self {
151        ParseError::Bound(e)
152    }
153}
154
155/// Scan `bytes`, then deserialize them into `T`.
156///
157/// Allocation discipline: the scan allocates only its container stack (at
158/// most `max_depth` frames) and one key set per open object (their total size
159/// is bounded by the input length, itself bounded by `max_bytes`). Typed
160/// deserialization runs only after the scan accepts, so its allocations are
161/// bounded by the accepted input. Reading the bytes from any transport is the
162/// caller's job and must be bounded separately.
163///
164/// `serde_json` applies its own recursion limit of 128; a `max_depth` above
165/// that can still yield a [`ParseError::Schema`] for deep input.
166pub fn parse_bounded<T: serde::de::DeserializeOwned>(
167    bytes: &[u8],
168    limits: &ParseLimits,
169) -> Result<T, ParseError> {
170    scan(bytes, limits)?;
171    serde_json::from_slice(bytes).map_err(|e| ParseError::Schema {
172        message: e.to_string(),
173    })
174}
175
176/// Validate `bytes` as exactly one JSON value within `limits`.
177///
178/// The length check against `max_bytes` happens before any byte is examined.
179/// Every other bound is checked incrementally, at the byte where it is first
180/// exceeded. The scan is iterative, so hostile nesting cannot exhaust the
181/// stack.
182pub fn scan(bytes: &[u8], limits: &ParseLimits) -> Result<ScanStats, BoundError> {
183    if bytes.len() > limits.max_bytes {
184        return Err(BoundError::TooLarge {
185            limit: limits.max_bytes,
186            actual: bytes.len(),
187        });
188    }
189    Scanner {
190        b: bytes,
191        pos: 0,
192        limits,
193        stack: Vec::new(),
194        values: 0,
195        max_depth_seen: 0,
196    }
197    .run()
198}
199
200enum Frame {
201    Array { len: usize },
202    Object { len: usize, keys: HashSet<String> },
203}
204
205struct Scanner<'a> {
206    b: &'a [u8],
207    pos: usize,
208    limits: &'a ParseLimits,
209    stack: Vec<Frame>,
210    values: usize,
211    max_depth_seen: usize,
212}
213
214enum Step {
215    Value,
216    AfterValue,
217}
218
219fn syntax(offset: usize, expected: &'static str) -> BoundError {
220    BoundError::Syntax { offset, expected }
221}
222
223impl Scanner<'_> {
224    fn peek(&self) -> Option<u8> {
225        self.b.get(self.pos).copied()
226    }
227
228    fn skip_ws(&mut self) {
229        while let Some(b' ' | b'\t' | b'\n' | b'\r') = self.peek() {
230            self.pos += 1;
231        }
232    }
233
234    fn run(mut self) -> Result<ScanStats, BoundError> {
235        self.skip_ws();
236        if self.pos == self.b.len() {
237            return Err(BoundError::Empty);
238        }
239        let mut step = Step::Value;
240        loop {
241            step = match step {
242                Step::Value => self.value()?,
243                Step::AfterValue => {
244                    self.skip_ws();
245                    match self.stack.last() {
246                        None => {
247                            if self.pos != self.b.len() {
248                                return Err(BoundError::TrailingData { offset: self.pos });
249                            }
250                            return Ok(ScanStats {
251                                bytes: self.b.len(),
252                                max_depth_seen: self.max_depth_seen,
253                                values: self.values,
254                            });
255                        }
256                        Some(Frame::Array { .. }) => match self.peek() {
257                            Some(b',') => {
258                                self.pos += 1;
259                                self.skip_ws();
260                                self.begin_member()?;
261                                Step::Value
262                            }
263                            Some(b']') => {
264                                self.pos += 1;
265                                self.stack.pop();
266                                Step::AfterValue
267                            }
268                            _ => return Err(syntax(self.pos, "',' or ']'")),
269                        },
270                        Some(Frame::Object { .. }) => match self.peek() {
271                            Some(b',') => {
272                                self.pos += 1;
273                                self.skip_ws();
274                                self.key()?;
275                                Step::Value
276                            }
277                            Some(b'}') => {
278                                self.pos += 1;
279                                self.stack.pop();
280                                Step::AfterValue
281                            }
282                            _ => return Err(syntax(self.pos, "',' or '}'")),
283                        },
284                    }
285                }
286            };
287        }
288    }
289
290    /// Count a new member of the innermost container.
291    fn begin_member(&mut self) -> Result<(), BoundError> {
292        let limit = self.limits.max_collection_len;
293        let offset = self.pos;
294        let len = match self.stack.last_mut() {
295            Some(Frame::Array { len }) | Some(Frame::Object { len, .. }) => len,
296            None => return Ok(()),
297        };
298        *len += 1;
299        if *len > limit {
300            return Err(BoundError::CollectionTooLong { limit, offset });
301        }
302        Ok(())
303    }
304
305    fn count_value(&mut self) -> Result<(), BoundError> {
306        self.values += 1;
307        if self.values > self.limits.max_total_values {
308            return Err(BoundError::TooManyValues {
309                limit: self.limits.max_total_values,
310                offset: self.pos,
311            });
312        }
313        Ok(())
314    }
315
316    fn push(&mut self, frame: Frame) -> Result<(), BoundError> {
317        if self.stack.len() >= self.limits.max_depth {
318            return Err(BoundError::TooDeep {
319                limit: self.limits.max_depth,
320                offset: self.pos,
321            });
322        }
323        self.stack.push(frame);
324        self.max_depth_seen = self.max_depth_seen.max(self.stack.len());
325        Ok(())
326    }
327
328    /// Parse an object key and its colon. Positioned at the opening quote.
329    fn key(&mut self) -> Result<(), BoundError> {
330        if self.peek() != Some(b'"') {
331            return Err(syntax(self.pos, "object key"));
332        }
333        self.begin_member()?;
334        let offset = self.pos;
335        let key = self.string(true)?.unwrap_or_default();
336        if let Some(Frame::Object { keys, .. }) = self.stack.last_mut() {
337            if keys.contains(&key) {
338                return Err(BoundError::DuplicateKey {
339                    key: key.chars().take(64).collect(),
340                    offset,
341                });
342            }
343            keys.insert(key);
344        }
345        self.skip_ws();
346        if self.peek() != Some(b':') {
347            return Err(syntax(self.pos, "':'"));
348        }
349        self.pos += 1;
350        self.skip_ws();
351        Ok(())
352    }
353
354    /// Parse the start of one value. Positioned at its first byte.
355    fn value(&mut self) -> Result<Step, BoundError> {
356        let Some(c) = self.peek() else {
357            return Err(syntax(self.pos, "value"));
358        };
359        match c {
360            b'{' => {
361                self.count_value()?;
362                self.push(Frame::Object {
363                    len: 0,
364                    keys: HashSet::new(),
365                })?;
366                self.pos += 1;
367                self.skip_ws();
368                if self.peek() == Some(b'}') {
369                    self.pos += 1;
370                    self.stack.pop();
371                    return Ok(Step::AfterValue);
372                }
373                self.key()?;
374                Ok(Step::Value)
375            }
376            b'[' => {
377                self.count_value()?;
378                self.push(Frame::Array { len: 0 })?;
379                self.pos += 1;
380                self.skip_ws();
381                if self.peek() == Some(b']') {
382                    self.pos += 1;
383                    self.stack.pop();
384                    return Ok(Step::AfterValue);
385                }
386                self.begin_member()?;
387                Ok(Step::Value)
388            }
389            b'"' => {
390                self.count_value()?;
391                self.string(false)?;
392                Ok(Step::AfterValue)
393            }
394            b'-' | b'0'..=b'9' => {
395                self.count_value()?;
396                self.number()?;
397                Ok(Step::AfterValue)
398            }
399            b't' => self.literal(b"true"),
400            b'f' => self.literal(b"false"),
401            b'n' => self.literal(b"null"),
402            _ => Err(syntax(self.pos, "value")),
403        }
404    }
405
406    fn literal(&mut self, word: &'static [u8]) -> Result<Step, BoundError> {
407        self.count_value()?;
408        if self.b[self.pos..].starts_with(word) {
409            self.pos += word.len();
410            Ok(Step::AfterValue)
411        } else {
412            Err(syntax(self.pos, "literal true, false or null"))
413        }
414    }
415
416    fn digits(&mut self, start: usize) -> Result<usize, BoundError> {
417        let mut n = 0;
418        while let Some(b'0'..=b'9') = self.peek() {
419            self.pos += 1;
420            n += 1;
421            self.check_number_len(start)?;
422        }
423        Ok(n)
424    }
425
426    fn check_number_len(&self, start: usize) -> Result<(), BoundError> {
427        if self.pos - start > self.limits.max_number_bytes {
428            return Err(BoundError::NumberTooLong {
429                limit: self.limits.max_number_bytes,
430                offset: start,
431            });
432        }
433        Ok(())
434    }
435
436    fn number(&mut self) -> Result<(), BoundError> {
437        let start = self.pos;
438        if self.peek() == Some(b'-') {
439            self.pos += 1;
440            self.check_number_len(start)?;
441        }
442        match self.peek() {
443            Some(b'0') => {
444                self.pos += 1;
445                self.check_number_len(start)?;
446            }
447            Some(b'1'..=b'9') => {
448                self.digits(start)?;
449            }
450            _ => return Err(syntax(self.pos, "digit")),
451        }
452        if self.peek() == Some(b'.') {
453            if !self.limits.allow_fractional_numbers {
454                return Err(BoundError::FractionalNumber { offset: self.pos });
455            }
456            self.pos += 1;
457            self.check_number_len(start)?;
458            if self.digits(start)? == 0 {
459                return Err(syntax(self.pos, "digit"));
460            }
461        }
462        if let Some(b'e' | b'E') = self.peek() {
463            if !self.limits.allow_fractional_numbers {
464                return Err(BoundError::FractionalNumber { offset: self.pos });
465            }
466            self.pos += 1;
467            self.check_number_len(start)?;
468            if let Some(b'+' | b'-') = self.peek() {
469                self.pos += 1;
470                self.check_number_len(start)?;
471            }
472            if self.digits(start)? == 0 {
473                return Err(syntax(self.pos, "digit"));
474            }
475        }
476        Ok(())
477    }
478
479    fn hex4(&mut self) -> Result<u32, BoundError> {
480        let mut v = 0u32;
481        for _ in 0..4 {
482            let d = match self.peek() {
483                Some(c @ b'0'..=b'9') => c - b'0',
484                Some(c @ b'a'..=b'f') => c - b'a' + 10,
485                Some(c @ b'A'..=b'F') => c - b'A' + 10,
486                _ => return Err(syntax(self.pos, "hex digit")),
487            };
488            v = v * 16 + u32::from(d);
489            self.pos += 1;
490        }
491        Ok(v)
492    }
493
494    /// Scan a string positioned at its opening quote. Returns the decoded
495    /// text when `decode` is true, and allocates nothing otherwise.
496    fn string(&mut self, decode: bool) -> Result<Option<String>, BoundError> {
497        let open = self.pos;
498        self.pos += 1;
499        let start = self.pos;
500        let limit = self.limits.max_string_bytes;
501        let mut out = decode.then(String::new);
502        loop {
503            if self.pos - start > limit {
504                return Err(BoundError::StringTooLong {
505                    limit,
506                    offset: open,
507                });
508            }
509            let Some(c) = self.peek() else {
510                return Err(syntax(self.pos, "closing '\"'"));
511            };
512            match c {
513                b'"' => {
514                    self.pos += 1;
515                    return Ok(out);
516                }
517                b'\\' => {
518                    self.pos += 1;
519                    let esc = self.peek();
520                    let ch = match esc {
521                        Some(b'"') => '"',
522                        Some(b'\\') => '\\',
523                        Some(b'/') => '/',
524                        Some(b'b') => '\u{8}',
525                        Some(b'f') => '\u{c}',
526                        Some(b'n') => '\n',
527                        Some(b'r') => '\r',
528                        Some(b't') => '\t',
529                        Some(b'u') => {
530                            let at = self.pos - 1;
531                            self.pos += 1;
532                            let hi = self.hex4()?;
533                            let cp = match hi {
534                                0xD800..=0xDBFF => {
535                                    if !self.b[self.pos..].starts_with(b"\\u") {
536                                        return Err(syntax(at, "valid surrogate pair"));
537                                    }
538                                    self.pos += 2;
539                                    let lo = self.hex4()?;
540                                    if !(0xDC00..=0xDFFF).contains(&lo) {
541                                        return Err(syntax(at, "valid surrogate pair"));
542                                    }
543                                    0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00)
544                                }
545                                0xDC00..=0xDFFF => {
546                                    return Err(syntax(at, "valid surrogate pair"));
547                                }
548                                v => v,
549                            };
550                            if let Some(s) = out.as_mut() {
551                                // Surrogates are excluded above, so this is a scalar value.
552                                s.push(char::from_u32(cp).unwrap_or('\u{fffd}'));
553                            }
554                            continue;
555                        }
556                        _ => return Err(syntax(self.pos, "escape character")),
557                    };
558                    self.pos += 1;
559                    if let Some(s) = out.as_mut() {
560                        s.push(ch);
561                    }
562                }
563                0x00..=0x1F => {
564                    return Err(syntax(self.pos, "escaped control character"));
565                }
566                0x20..=0x7F => {
567                    self.pos += 1;
568                    if let Some(s) = out.as_mut() {
569                        s.push(char::from(c));
570                    }
571                }
572                _ => {
573                    let width = match c {
574                        0xC2..=0xDF => 2,
575                        0xE0..=0xEF => 3,
576                        0xF0..=0xF4 => 4,
577                        _ => return Err(BoundError::InvalidUtf8 { offset: self.pos }),
578                    };
579                    let end = self.pos + width;
580                    let Some(span) = self.b.get(self.pos..end) else {
581                        return Err(BoundError::InvalidUtf8 { offset: self.pos });
582                    };
583                    let Ok(text) = std::str::from_utf8(span) else {
584                        return Err(BoundError::InvalidUtf8 { offset: self.pos });
585                    };
586                    if let Some(s) = out.as_mut() {
587                        s.push_str(text);
588                    }
589                    self.pos = end;
590                }
591            }
592        }
593    }
594}
595
596#[cfg(test)]
597mod tests {
598    use super::*;
599
600    const WIDE: ParseLimits = ParseLimits {
601        max_bytes: 1 << 20,
602        max_depth: 64,
603        max_string_bytes: 1 << 16,
604        max_collection_len: 1 << 16,
605        max_total_values: 1 << 20,
606        max_number_bytes: 64,
607        allow_fractional_numbers: true,
608    };
609
610    fn ok(s: &str, l: &ParseLimits) -> ScanStats {
611        scan(s.as_bytes(), l).unwrap_or_else(|e| panic!("{s:?}: {e}"))
612    }
613
614    fn err(s: &str, l: &ParseLimits) -> BoundError {
615        scan(s.as_bytes(), l).expect_err(s)
616    }
617
618    #[test]
619    fn accepts_valid_documents() {
620        for s in [
621            "0",
622            "-0",
623            "1.5e-3",
624            "-12E+4",
625            "true",
626            "false",
627            "null",
628            "\"\"",
629            "\"a\\n\\u00e9\\ud83d\\ude00\"",
630            "\"é😀\"",
631            " [ ] ",
632            "{}",
633            "{\"a\":[1,{\"b\":null}],\"c\":\"d\"}",
634            "\r\n\t[1 , 2]\n",
635        ] {
636            ok(s, &WIDE);
637        }
638    }
639
640    #[test]
641    fn stats() {
642        let s = ok("{\"a\":[1,2,{}],\"b\":\"x\"}", &WIDE);
643        assert_eq!(s.max_depth_seen, 3);
644        // object, array, 1, 2, {}, "x"
645        assert_eq!(s.values, 6);
646        assert_eq!(ok("7", &WIDE).max_depth_seen, 0);
647    }
648
649    #[test]
650    fn syntax_errors() {
651        for s in [
652            "[1,]",
653            "[1 2]",
654            "{\"a\" 1}",
655            "{\"a\":}",
656            "{1:2}",
657            "{\"a\":1,}",
658            "tru",
659            "nul",
660            "01x",
661            "[01]",
662            "-",
663            "1.",
664            "1e",
665            "1e+",
666            ".5",
667            "+1",
668            "\"abc",
669            "\"\\x\"",
670            "\"\\u12G4\"",
671            "[",
672            "{",
673            "'a'",
674            "\"a\tb\"",
675            "\"\u{1}\"",
676        ] {
677            let e = err(s, &WIDE);
678            assert!(
679                matches!(
680                    e,
681                    BoundError::Syntax { .. } | BoundError::TrailingData { .. }
682                ),
683                "{s:?}: {e:?}"
684            );
685        }
686    }
687
688    #[test]
689    fn empty_and_trailing() {
690        assert_eq!(err("", &WIDE), BoundError::Empty);
691        assert_eq!(err(" \n\t ", &WIDE), BoundError::Empty);
692        assert_eq!(err("1 2", &WIDE), BoundError::TrailingData { offset: 2 });
693        assert_eq!(err("{} {}", &WIDE), BoundError::TrailingData { offset: 3 });
694        assert_eq!(err("01", &WIDE), BoundError::TrailingData { offset: 1 });
695    }
696
697    #[test]
698    fn too_large_checked_first() {
699        let l = ParseLimits {
700            max_bytes: 4,
701            ..WIDE
702        };
703        ok("[12]", &l);
704        // Invalid content but over the limit: size is reported, not syntax.
705        assert_eq!(
706            err("#####", &l),
707            BoundError::TooLarge {
708                limit: 4,
709                actual: 5
710            }
711        );
712        let zero = ParseLimits {
713            max_bytes: 0,
714            ..WIDE
715        };
716        assert_eq!(err("", &zero), BoundError::Empty);
717    }
718
719    #[test]
720    fn depth_limit() {
721        let l = ParseLimits {
722            max_depth: 3,
723            ..WIDE
724        };
725        assert_eq!(ok("[[[1]]]", &l).max_depth_seen, 3);
726        assert_eq!(ok("{\"a\":{\"b\":[]}}", &l).max_depth_seen, 3);
727        assert_eq!(
728            err("[[[[1]]]]", &l),
729            BoundError::TooDeep {
730                limit: 3,
731                offset: 3
732            }
733        );
734        let flat = ParseLimits {
735            max_depth: 0,
736            ..WIDE
737        };
738        ok("\"scalar\"", &flat);
739        assert!(matches!(err("[]", &flat), BoundError::TooDeep { .. }));
740    }
741
742    #[test]
743    fn deep_nesting_is_refused_without_recursion() {
744        let n = 10_000;
745        let s = "[".repeat(n) + &"]".repeat(n);
746        assert!(matches!(
747            err(&s, &WIDE),
748            BoundError::TooDeep { limit: 64, .. }
749        ));
750        let deep = ParseLimits {
751            max_depth: n,
752            ..WIDE
753        };
754        assert_eq!(ok(&s, &deep).max_depth_seen, n);
755        let obj = "{\"a\":".repeat(n) + "1" + &"}".repeat(n);
756        assert_eq!(ok(&obj, &deep).max_depth_seen, n);
757    }
758
759    #[test]
760    fn string_limit() {
761        let l = ParseLimits {
762            max_string_bytes: 3,
763            ..WIDE
764        };
765        ok("\"abc\"", &l);
766        ok("{\"abc\":\"xyz\"}", &l);
767        // Escapes count by raw length: "\n" is 2 raw bytes.
768        ok("\"a\\n\"", &l);
769        assert_eq!(
770            err("\"abcd\"", &l),
771            BoundError::StringTooLong {
772                limit: 3,
773                offset: 0
774            }
775        );
776        assert!(matches!(
777            err("{\"abcd\":1}", &l),
778            BoundError::StringTooLong { .. }
779        ));
780        assert!(matches!(
781            err("\"\\u0041\"", &l),
782            BoundError::StringTooLong { .. }
783        ));
784        // Multi-byte characters count their bytes: "é" is 2 bytes, "éé" is 4.
785        ok("\"é\"", &l);
786        assert!(matches!(
787            err("\"éé\"", &l),
788            BoundError::StringTooLong { .. }
789        ));
790    }
791
792    #[test]
793    fn string_limit_fires_before_end_of_input() {
794        let l = ParseLimits {
795            max_string_bytes: 3,
796            ..WIDE
797        };
798        // Unterminated: an unbounded scan would report a syntax error at the end.
799        assert!(matches!(
800            err("\"abcdefgh", &l),
801            BoundError::StringTooLong { .. }
802        ));
803    }
804
805    #[test]
806    fn collection_limit() {
807        let l = ParseLimits {
808            max_collection_len: 2,
809            ..WIDE
810        };
811        ok("[1,2]", &l);
812        ok("{\"a\":1,\"b\":2}", &l);
813        assert_eq!(
814            err("[1,2,3]", &l),
815            BoundError::CollectionTooLong {
816                limit: 2,
817                offset: 5
818            }
819        );
820        assert!(matches!(
821            err("{\"a\":1,\"b\":2,\"c\":3}", &l),
822            BoundError::CollectionTooLong { .. }
823        ));
824        // Incremental: refused before the malformed tail is reached.
825        assert!(matches!(
826            err("[1,2,3,#", &l),
827            BoundError::CollectionTooLong { .. }
828        ));
829    }
830
831    #[test]
832    fn total_value_limit() {
833        let l = ParseLimits {
834            max_total_values: 3,
835            ..WIDE
836        };
837        ok("[1,2]", &l);
838        ok("{\"a\":1,\"b\":2}", &l);
839        assert_eq!(
840            err("[1,2,3]", &l),
841            BoundError::TooManyValues {
842                limit: 3,
843                offset: 5
844            }
845        );
846        assert!(matches!(
847            err("[[],[],[]]", &l),
848            BoundError::TooManyValues { .. }
849        ));
850    }
851
852    #[test]
853    fn number_limit() {
854        let l = ParseLimits {
855            max_number_bytes: 4,
856            ..WIDE
857        };
858        ok("1234", &l);
859        ok("-123", &l);
860        ok("1.25", &l);
861        assert_eq!(
862            err("12345", &l),
863            BoundError::NumberTooLong {
864                limit: 4,
865                offset: 0
866            }
867        );
868        assert!(matches!(
869            err("[-1234]", &l),
870            BoundError::NumberTooLong { offset: 1, .. }
871        ));
872        assert!(matches!(err("1.234", &l), BoundError::NumberTooLong { .. }));
873        assert!(matches!(err("1e+10", &l), BoundError::NumberTooLong { .. }));
874    }
875
876    #[test]
877    fn fractional_numbers() {
878        let l = ParseLimits {
879            allow_fractional_numbers: false,
880            ..WIDE
881        };
882        ok("-42", &l);
883        ok("[0,1,2]", &l);
884        assert_eq!(err("1.5", &l), BoundError::FractionalNumber { offset: 1 });
885        assert_eq!(err("[1e3]", &l), BoundError::FractionalNumber { offset: 2 });
886        assert_eq!(err("2E-1", &l), BoundError::FractionalNumber { offset: 1 });
887        ok("1.5", &WIDE);
888        ok("1e3", &WIDE);
889    }
890
891    #[test]
892    fn duplicate_keys() {
893        assert_eq!(
894            err("{\"a\":1,\"a\":2}", &WIDE),
895            BoundError::DuplicateKey {
896                key: "a".into(),
897                offset: 7
898            }
899        );
900        // Compared after decoding escapes.
901        assert!(matches!(
902            err("{\"a\":1,\"\\u0061\":2}", &WIDE),
903            BoundError::DuplicateKey { .. }
904        ));
905        assert!(matches!(
906            err("{\"\\/\":1,\"/\":2}", &WIDE),
907            BoundError::DuplicateKey { .. }
908        ));
909        // Same key in sibling and nested objects is fine.
910        ok("{\"a\":{\"a\":1},\"b\":[{\"a\":1},{\"a\":2}]}", &WIDE);
911        // Duplicate only in an inner object is still refused.
912        assert!(matches!(
913            err("{\"x\":{\"a\":1,\"a\":2}}", &WIDE),
914            BoundError::DuplicateKey { .. }
915        ));
916        // Reported key is truncated to 64 characters.
917        let long = "k".repeat(100);
918        let s = format!("{{\"{long}\":1,\"{long}\":2}}");
919        match err(&s, &WIDE) {
920            BoundError::DuplicateKey { key, .. } => assert_eq!(key.chars().count(), 64),
921            e => panic!("{e:?}"),
922        }
923    }
924
925    #[test]
926    fn surrogates() {
927        ok("\"\\ud83d\\ude00\"", &WIDE);
928        ok("\"\\uD83D\\uDE00\"", &WIDE);
929        for s in [
930            "\"\\ud83d\"",
931            "\"\\ud83dx\"",
932            "\"\\ud83d\\u0041\"",
933            "\"\\ude00\"",
934            "\"\\ude00\\ud83d\"",
935            "\"\\ud83d\\n\"",
936        ] {
937            assert!(matches!(err(s, &WIDE), BoundError::Syntax { .. }), "{s}");
938        }
939        // Decoded pair participates in duplicate detection.
940        assert!(matches!(
941            err("{\"😀\":1,\"\\ud83d\\ude00\":2}", &WIDE),
942            BoundError::DuplicateKey { .. }
943        ));
944    }
945
946    #[test]
947    fn invalid_utf8() {
948        let cases: [&[u8]; 6] = [
949            b"\"\xff\"",
950            b"\"\xc0\xaf\"",
951            b"\"\xe0\x80\xaf\"",
952            b"\"\xed\xa0\x80\"",
953            b"\"\xf5\x80\x80\x80\"",
954            b"\"\xc3",
955        ];
956        for c in cases {
957            assert_eq!(
958                scan(c, &WIDE),
959                Err(BoundError::InvalidUtf8 { offset: 1 }),
960                "{c:?}"
961            );
962        }
963    }
964
965    #[derive(serde::Deserialize, Debug, PartialEq)]
966    #[serde(deny_unknown_fields)]
967    struct Doc {
968        name: String,
969        n: u32,
970    }
971
972    #[test]
973    fn parse_bounded_typed() {
974        let d: Doc = parse_bounded(br#"{"name":"x","n":3}"#, &WIDE).unwrap();
975        assert_eq!(
976            d,
977            Doc {
978                name: "x".into(),
979                n: 3
980            }
981        );
982        let e = parse_bounded::<Doc>(br#"{"name":"x","n":3,"extra":1}"#, &WIDE).unwrap_err();
983        assert!(matches!(e, ParseError::Schema { ref message } if message.contains("extra")));
984        // serde_json alone would silently keep the last duplicate; the scan refuses.
985        let e = parse_bounded::<Doc>(br#"{"name":"x","n":3,"n":4}"#, &WIDE).unwrap_err();
986        assert!(matches!(
987            e,
988            ParseError::Bound(BoundError::DuplicateKey { .. })
989        ));
990        // A bound refusal happens before the typed parse is attempted.
991        let l = ParseLimits {
992            max_string_bytes: 2,
993            ..WIDE
994        };
995        let e = parse_bounded::<Doc>(br#"{"name":"xyz","n":3}"#, &l).unwrap_err();
996        assert!(matches!(
997            e,
998            ParseError::Bound(BoundError::StringTooLong { .. })
999        ));
1000        let e = parse_bounded::<Doc>(
1001            br#"{"name":"x","n":3.5}"#,
1002            &ParseLimits {
1003                allow_fractional_numbers: false,
1004                ..WIDE
1005            },
1006        )
1007        .unwrap_err();
1008        assert!(matches!(
1009            e,
1010            ParseError::Bound(BoundError::FractionalNumber { .. })
1011        ));
1012        assert!(e.to_string().starts_with("bounds:"));
1013    }
1014
1015    #[test]
1016    fn display_is_informative() {
1017        let e = BoundError::TooLarge {
1018            limit: 1,
1019            actual: 2,
1020        };
1021        assert_eq!(e.to_string(), "input is 2 bytes, limit 1");
1022    }
1023}