facet_json/
adapter.rs

1//! Token adapter that bridges Scanner to the deserializer.
2//!
3//! The adapter provides two methods:
4//! - `next_token()` - returns decoded token content
5//! - `skip()` - skips a value without allocation, returns span
6//!
7//! This design allows the deserializer to avoid allocations when:
8//! - Skipping unknown fields
9//! - Capturing RawJson (just need span)
10
11use alloc::borrow::Cow;
12
13use facet_reflect::Span;
14
15use crate::scanner::{self, ParsedNumber, ScanError, ScanErrorKind, Scanner, Token as ScanToken};
16
17/// Token with decoded content, ready for deserialization.
18#[derive(Debug, Clone, PartialEq)]
19pub enum Token<'input> {
20    /// `{`
21    ObjectStart,
22    /// `}`
23    ObjectEnd,
24    /// `[`
25    ArrayStart,
26    /// `]`
27    ArrayEnd,
28    /// `:`
29    Colon,
30    /// `,`
31    Comma,
32    /// `null`
33    Null,
34    /// `true`
35    True,
36    /// `false`
37    False,
38    /// String value (decoded)
39    String(Cow<'input, str>),
40    /// Unsigned 64-bit integer
41    U64(u64),
42    /// Signed 64-bit integer
43    I64(i64),
44    /// Unsigned 128-bit integer
45    U128(u128),
46    /// Signed 128-bit integer
47    I128(i128),
48    /// 64-bit float
49    F64(f64),
50    /// End of input
51    Eof,
52}
53
54/// Spanned token with location information.
55#[derive(Debug, Clone)]
56pub struct SpannedAdapterToken<'input> {
57    /// The token
58    pub token: Token<'input>,
59    /// Source span
60    pub span: Span,
61}
62
63/// Adapter error (wraps scanner errors).
64#[derive(Debug, Clone)]
65pub struct AdapterError {
66    /// The error kind
67    pub kind: AdapterErrorKind,
68    /// Source span
69    pub span: Span,
70}
71
72/// Types of adapter errors.
73#[derive(Debug, Clone)]
74pub enum AdapterErrorKind {
75    /// Scanner error
76    Scan(ScanErrorKind),
77    /// Need more data (for streaming)
78    NeedMore,
79}
80
81impl From<ScanError> for AdapterError {
82    fn from(e: ScanError) -> Self {
83        AdapterError {
84            kind: AdapterErrorKind::Scan(e.kind),
85            span: e.span,
86        }
87    }
88}
89
90/// Default chunk size for windowed scanning (small for testing boundary conditions)
91pub const DEFAULT_CHUNK_SIZE: usize = 4;
92
93/// Token adapter for slice-based parsing with fixed-size windowing.
94///
95/// Uses a sliding window approach:
96/// - Scanner sees only `chunk_size` bytes at a time
97/// - On `NeedMore`, window grows (extends end) to include more bytes
98/// - On token complete, window slides (moves start) past consumed bytes
99/// - No data is copied - window is just a view into the original slice
100///
101/// The const generic `BORROW` controls string handling:
102/// - `BORROW=true`: strings without escapes are borrowed (`Cow::Borrowed`)
103/// - `BORROW=false`: all strings are owned (`Cow::Owned`)
104pub struct SliceAdapter<'input, const BORROW: bool> {
105    /// Full original input (for borrowing strings)
106    input: &'input [u8],
107    /// Start of current window in input
108    window_start: usize,
109    /// End of current window in input
110    window_end: usize,
111    /// Chunk size for window growth
112    chunk_size: usize,
113    /// The scanner
114    scanner: Scanner,
115}
116
117impl<'input, const BORROW: bool> SliceAdapter<'input, BORROW> {
118    /// Create a new adapter with the default chunk size (4 bytes).
119    pub fn new(input: &'input [u8]) -> Self {
120        Self::with_chunk_size(input, DEFAULT_CHUNK_SIZE)
121    }
122
123    /// Create a new adapter with a custom chunk size.
124    pub fn with_chunk_size(input: &'input [u8], chunk_size: usize) -> Self {
125        let initial_end = chunk_size.min(input.len());
126        Self {
127            input,
128            window_start: 0,
129            window_end: initial_end,
130            chunk_size,
131            scanner: Scanner::new(),
132        }
133    }
134
135    /// Get the current window into the input.
136    #[inline]
137    fn current_window(&self) -> &'input [u8] {
138        &self.input[self.window_start..self.window_end]
139    }
140
141    /// Grow the window by one chunk (or to end of input).
142    #[inline]
143    fn grow_window(&mut self) {
144        self.window_end = (self.window_end + self.chunk_size).min(self.input.len());
145    }
146
147    /// Slide the window forward past consumed bytes, reset scanner.
148    #[inline]
149    fn slide_window(&mut self, consumed_in_window: usize) {
150        self.window_start += consumed_in_window;
151        self.window_end = (self.window_start + self.chunk_size).min(self.input.len());
152        self.scanner.set_pos(0);
153    }
154
155    /// Check if we've reached the end of input.
156    #[inline]
157    fn at_end_of_input(&self) -> bool {
158        self.window_end >= self.input.len()
159    }
160
161    /// Get the next token with decoded content.
162    ///
163    /// Strings are decoded (escapes processed) and returned as `Cow<str>`.
164    /// Numbers are parsed into appropriate numeric types.
165    ///
166    /// Uses windowed scanning: on `NeedMore`, grows the window and retries.
167    /// Spans are absolute positions in the original input.
168    pub fn next_token(&mut self) -> Result<SpannedAdapterToken<'input>, AdapterError> {
169        loop {
170            let window = self.current_window();
171            let spanned = match self.scanner.next_token(window) {
172                Ok(s) => s,
173                Err(e) => {
174                    // Translate error span to absolute position
175                    return Err(AdapterError {
176                        kind: AdapterErrorKind::Scan(e.kind),
177                        span: Span::new(self.window_start + e.span.offset, e.span.len),
178                    });
179                }
180            };
181
182            match spanned.token {
183                ScanToken::NeedMore { .. } => {
184                    // Need more data - grow window if possible
185                    if self.at_end_of_input() {
186                        // True EOF - try to finalize any pending token (e.g., number at EOF)
187                        let window = self.current_window();
188                        let finalized = match self.scanner.finalize_at_eof(window) {
189                            Ok(f) => f,
190                            Err(e) => {
191                                return Err(AdapterError {
192                                    kind: AdapterErrorKind::Scan(e.kind),
193                                    span: Span::new(self.window_start + e.span.offset, e.span.len),
194                                });
195                            }
196                        };
197
198                        // Handle the finalized token
199                        let consumed = self.scanner.pos();
200                        let absolute_span = Span::new(
201                            self.window_start + finalized.span.offset,
202                            finalized.span.len,
203                        );
204
205                        let token = self.materialize_token(&finalized)?;
206                        self.slide_window(consumed);
207
208                        return Ok(SpannedAdapterToken {
209                            token,
210                            span: absolute_span,
211                        });
212                    }
213                    self.grow_window();
214                    continue;
215                }
216                ScanToken::Eof => {
217                    // Scanner hit end of window
218                    if self.at_end_of_input() {
219                        // True EOF
220                        return Ok(SpannedAdapterToken {
221                            token: Token::Eof,
222                            span: Span::new(self.window_start + spanned.span.offset, 0),
223                        });
224                    }
225                    // End of window but more input available - slide forward
226                    self.slide_window(self.scanner.pos());
227                    continue;
228                }
229                _ => {
230                    // Complete token - materialize and return
231                    let consumed = self.scanner.pos();
232                    let absolute_span =
233                        Span::new(self.window_start + spanned.span.offset, spanned.span.len);
234
235                    let token = self.materialize_token(&spanned)?;
236
237                    // Slide window past this token for next call
238                    self.slide_window(consumed);
239
240                    return Ok(SpannedAdapterToken {
241                        token,
242                        span: absolute_span,
243                    });
244                }
245            }
246        }
247    }
248
249    /// Materialize a scanned token into a decoded token.
250    ///
251    /// Positions in `spanned` are relative to current window.
252    /// We borrow/decode from the original input slice using absolute positions.
253    fn materialize_token(
254        &self,
255        spanned: &scanner::SpannedToken,
256    ) -> Result<Token<'input>, AdapterError> {
257        match &spanned.token {
258            ScanToken::ObjectStart => Ok(Token::ObjectStart),
259            ScanToken::ObjectEnd => Ok(Token::ObjectEnd),
260            ScanToken::ArrayStart => Ok(Token::ArrayStart),
261            ScanToken::ArrayEnd => Ok(Token::ArrayEnd),
262            ScanToken::Colon => Ok(Token::Colon),
263            ScanToken::Comma => Ok(Token::Comma),
264            ScanToken::Null => Ok(Token::Null),
265            ScanToken::True => Ok(Token::True),
266            ScanToken::False => Ok(Token::False),
267            ScanToken::String {
268                start,
269                end,
270                has_escapes,
271            } => {
272                // Convert to absolute positions in original input
273                let abs_start = self.window_start + start;
274                let abs_end = self.window_start + end;
275
276                let s = if BORROW && !*has_escapes {
277                    // Borrow directly from original input (zero-copy)
278                    scanner::decode_string(self.input, abs_start, abs_end, false)?
279                } else {
280                    // Must produce owned string (has escapes or BORROW=false)
281                    Cow::Owned(scanner::decode_string_owned(
282                        self.input, abs_start, abs_end,
283                    )?)
284                };
285                Ok(Token::String(s))
286            }
287            ScanToken::Number { start, end, hint } => {
288                // Convert to absolute positions
289                let abs_start = self.window_start + start;
290                let abs_end = self.window_start + end;
291
292                let parsed = scanner::parse_number(self.input, abs_start, abs_end, *hint)?;
293                Ok(match parsed {
294                    ParsedNumber::U64(n) => Token::U64(n),
295                    ParsedNumber::I64(n) => Token::I64(n),
296                    ParsedNumber::U128(n) => Token::U128(n),
297                    ParsedNumber::I128(n) => Token::I128(n),
298                    ParsedNumber::F64(n) => Token::F64(n),
299                })
300            }
301            ScanToken::Eof | ScanToken::NeedMore { .. } => {
302                unreachable!("Eof and NeedMore handled in next_token loop")
303            }
304        }
305    }
306
307    /// Skip a JSON value without decoding.
308    ///
309    /// Returns the span of the skipped value (absolute positions).
310    /// No string allocations occur.
311    pub fn skip(&mut self) -> Result<Span, AdapterError> {
312        // Get the first token using windowing
313        let first_token = self.next_token_for_skip()?;
314        let abs_start = first_token.span.offset;
315
316        match first_token.token {
317            SkipToken::ObjectStart => {
318                // Skip until matching ObjectEnd
319                let mut depth = 1;
320                let mut abs_end = first_token.span.offset + first_token.span.len;
321                while depth > 0 {
322                    let t = self.next_token_for_skip()?;
323                    abs_end = t.span.offset + t.span.len;
324                    match t.token {
325                        SkipToken::ObjectStart => depth += 1,
326                        SkipToken::ObjectEnd => depth -= 1,
327                        _ => {}
328                    }
329                }
330                Ok(Span::new(abs_start, abs_end - abs_start))
331            }
332            SkipToken::ArrayStart => {
333                // Skip until matching ArrayEnd
334                let mut depth = 1;
335                let mut abs_end = first_token.span.offset + first_token.span.len;
336                while depth > 0 {
337                    let t = self.next_token_for_skip()?;
338                    abs_end = t.span.offset + t.span.len;
339                    match t.token {
340                        SkipToken::ArrayStart => depth += 1,
341                        SkipToken::ArrayEnd => depth -= 1,
342                        _ => {}
343                    }
344                }
345                Ok(Span::new(abs_start, abs_end - abs_start))
346            }
347            // Scalars: just return their span
348            SkipToken::Scalar => Ok(first_token.span),
349            SkipToken::Eof => Err(AdapterError {
350                kind: AdapterErrorKind::Scan(ScanErrorKind::UnexpectedEof("expected value")),
351                span: first_token.span,
352            }),
353            // These shouldn't appear as first token when skipping a value
354            SkipToken::ObjectEnd | SkipToken::ArrayEnd => Err(AdapterError {
355                kind: AdapterErrorKind::Scan(ScanErrorKind::UnexpectedChar('}')),
356                span: first_token.span,
357            }),
358        }
359    }
360
361    /// Internal: get next token for skip operation (handles windowing).
362    fn next_token_for_skip(&mut self) -> Result<SpannedSkipToken, AdapterError> {
363        loop {
364            let window = self.current_window();
365            let spanned = match self.scanner.next_token(window) {
366                Ok(s) => s,
367                Err(e) => {
368                    return Err(AdapterError {
369                        kind: AdapterErrorKind::Scan(e.kind),
370                        span: Span::new(self.window_start + e.span.offset, e.span.len),
371                    });
372                }
373            };
374
375            match spanned.token {
376                ScanToken::NeedMore { .. } => {
377                    if self.at_end_of_input() {
378                        // True EOF - try to finalize any pending token
379                        let window = self.current_window();
380                        let finalized = match self.scanner.finalize_at_eof(window) {
381                            Ok(f) => f,
382                            Err(e) => {
383                                return Err(AdapterError {
384                                    kind: AdapterErrorKind::Scan(e.kind),
385                                    span: Span::new(self.window_start + e.span.offset, e.span.len),
386                                });
387                            }
388                        };
389
390                        let consumed = self.scanner.pos();
391                        let abs_span = Span::new(
392                            self.window_start + finalized.span.offset,
393                            finalized.span.len,
394                        );
395
396                        let skip_token = match finalized.token {
397                            ScanToken::ObjectStart => SkipToken::ObjectStart,
398                            ScanToken::ObjectEnd => SkipToken::ObjectEnd,
399                            ScanToken::ArrayStart => SkipToken::ArrayStart,
400                            ScanToken::ArrayEnd => SkipToken::ArrayEnd,
401                            ScanToken::String { .. }
402                            | ScanToken::Number { .. }
403                            | ScanToken::True
404                            | ScanToken::False
405                            | ScanToken::Null
406                            | ScanToken::Colon
407                            | ScanToken::Comma => SkipToken::Scalar,
408                            ScanToken::Eof => SkipToken::Eof,
409                            ScanToken::NeedMore { .. } => unreachable!(),
410                        };
411
412                        self.slide_window(consumed);
413                        return Ok(SpannedSkipToken {
414                            token: skip_token,
415                            span: abs_span,
416                        });
417                    }
418                    self.grow_window();
419                    continue;
420                }
421                ScanToken::Eof => {
422                    if self.at_end_of_input() {
423                        return Ok(SpannedSkipToken {
424                            token: SkipToken::Eof,
425                            span: Span::new(self.window_start + spanned.span.offset, 0),
426                        });
427                    }
428                    self.slide_window(self.scanner.pos());
429                    continue;
430                }
431                _ => {
432                    let consumed = self.scanner.pos();
433                    let abs_span =
434                        Span::new(self.window_start + spanned.span.offset, spanned.span.len);
435
436                    let skip_token = match spanned.token {
437                        ScanToken::ObjectStart => SkipToken::ObjectStart,
438                        ScanToken::ObjectEnd => SkipToken::ObjectEnd,
439                        ScanToken::ArrayStart => SkipToken::ArrayStart,
440                        ScanToken::ArrayEnd => SkipToken::ArrayEnd,
441                        ScanToken::String { .. }
442                        | ScanToken::Number { .. }
443                        | ScanToken::True
444                        | ScanToken::False
445                        | ScanToken::Null
446                        | ScanToken::Colon
447                        | ScanToken::Comma => SkipToken::Scalar,
448                        ScanToken::Eof | ScanToken::NeedMore { .. } => unreachable!(),
449                    };
450
451                    self.slide_window(consumed);
452                    return Ok(SpannedSkipToken {
453                        token: skip_token,
454                        span: abs_span,
455                    });
456                }
457            }
458        }
459    }
460
461    /// Get the current absolute position in the input.
462    pub fn position(&self) -> usize {
463        self.window_start + self.scanner.pos()
464    }
465
466    /// Get the underlying input slice.
467    pub fn input(&self) -> &'input [u8] {
468        self.input
469    }
470}
471
472/// Simplified token type for skip operations (no need to decode content).
473#[derive(Debug, Clone, Copy)]
474enum SkipToken {
475    ObjectStart,
476    ObjectEnd,
477    ArrayStart,
478    ArrayEnd,
479    Scalar, // String, Number, true, false, null, colon, comma
480    Eof,
481}
482
483/// Spanned skip token.
484#[derive(Debug)]
485struct SpannedSkipToken {
486    token: SkipToken,
487    span: Span,
488}
489
490// ============================================================================
491// TokenSource trait - unifies slice and streaming adapters
492// ============================================================================
493
494use crate::{JsonError, JsonErrorKind};
495
496/// Trait for token sources that can be used by the deserializer.
497///
498/// This trait abstracts over both:
499/// - `SliceAdapter<'input>` implements `TokenSource<'input>` - can borrow from input
500/// - `StreamingAdapter` implements `TokenSource<'static>` - always owned
501///
502/// The lifetime parameter `'input` is the lifetime of the input data,
503/// NOT the lifetime of `self`. This is why we don't need GATs.
504pub trait TokenSource<'input> {
505    /// Get the next token.
506    fn next_token(&mut self) -> Result<SpannedAdapterToken<'input>, JsonError>;
507
508    /// Skip a JSON value without fully decoding it.
509    /// Returns the span of the skipped value.
510    fn skip(&mut self) -> Result<Span, JsonError>;
511
512    /// Get the raw input bytes, if available.
513    ///
514    /// Returns `Some` for slice-based adapters, `None` for streaming.
515    /// Used for features that need direct input access (RawJson, flatten replay).
516    fn input_bytes(&self) -> Option<&'input [u8]> {
517        None
518    }
519
520    /// Create a new adapter starting from the given offset in the input.
521    ///
522    /// Returns `Some` for slice-based adapters, `None` for streaming.
523    /// Used for flatten replay.
524    fn at_offset(&self, offset: usize) -> Option<Self>
525    where
526        Self: Sized,
527    {
528        let _ = offset;
529        None
530    }
531}
532
533impl<'input, const BORROW: bool> TokenSource<'input> for SliceAdapter<'input, BORROW> {
534    fn next_token(&mut self) -> Result<SpannedAdapterToken<'input>, JsonError> {
535        SliceAdapter::next_token(self).map_err(|e| JsonError {
536            kind: JsonErrorKind::Scan(match e.kind {
537                AdapterErrorKind::Scan(s) => s,
538                AdapterErrorKind::NeedMore => {
539                    crate::scanner::ScanErrorKind::UnexpectedEof("need more data")
540                }
541            }),
542            span: Some(e.span),
543            source_code: None,
544        })
545    }
546
547    fn skip(&mut self) -> Result<Span, JsonError> {
548        SliceAdapter::skip(self).map_err(|e| JsonError {
549            kind: JsonErrorKind::Scan(match e.kind {
550                AdapterErrorKind::Scan(s) => s,
551                AdapterErrorKind::NeedMore => {
552                    crate::scanner::ScanErrorKind::UnexpectedEof("need more data")
553                }
554            }),
555            span: Some(e.span),
556            source_code: None,
557        })
558    }
559
560    fn input_bytes(&self) -> Option<&'input [u8]> {
561        Some(self.input)
562    }
563
564    fn at_offset(&self, offset: usize) -> Option<Self> {
565        Some(SliceAdapter::new(&self.input[offset..]))
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572
573    #[test]
574    fn test_next_simple() {
575        let json = br#"{"name": "test", "value": 42}"#;
576        let mut adapter = SliceAdapter::<true>::new(json);
577
578        // {
579        let t = adapter.next_token().unwrap();
580        assert!(matches!(t.token, Token::ObjectStart));
581
582        // "name"
583        let t = adapter.next_token().unwrap();
584        assert_eq!(t.token, Token::String(Cow::Borrowed("name")));
585
586        // :
587        let t = adapter.next_token().unwrap();
588        assert!(matches!(t.token, Token::Colon));
589
590        // "test"
591        let t = adapter.next_token().unwrap();
592        assert_eq!(t.token, Token::String(Cow::Borrowed("test")));
593
594        // ,
595        let t = adapter.next_token().unwrap();
596        assert!(matches!(t.token, Token::Comma));
597
598        // "value"
599        let t = adapter.next_token().unwrap();
600        assert_eq!(t.token, Token::String(Cow::Borrowed("value")));
601
602        // :
603        let t = adapter.next_token().unwrap();
604        assert!(matches!(t.token, Token::Colon));
605
606        // 42
607        let t = adapter.next_token().unwrap();
608        assert_eq!(t.token, Token::U64(42));
609
610        // }
611        let t = adapter.next_token().unwrap();
612        assert!(matches!(t.token, Token::ObjectEnd));
613
614        // EOF
615        let t = adapter.next_token().unwrap();
616        assert!(matches!(t.token, Token::Eof));
617    }
618
619    #[test]
620    fn test_next_with_escapes() {
621        let json = br#""hello\nworld""#;
622        let mut adapter = SliceAdapter::<true>::new(json);
623
624        let t = adapter.next_token().unwrap();
625        // Has escapes, so it's Owned
626        assert_eq!(
627            t.token,
628            Token::String(Cow::Owned("hello\nworld".to_string()))
629        );
630    }
631
632    #[test]
633    fn test_skip_scalar() {
634        let json = br#"{"skip": 12345, "keep": "value"}"#;
635        let mut adapter = SliceAdapter::<true>::new(json);
636
637        // {
638        adapter.next_token().unwrap();
639        // "skip"
640        adapter.next_token().unwrap();
641        // :
642        adapter.next_token().unwrap();
643
644        // Skip the number - no allocation!
645        let span = adapter.skip().unwrap();
646        assert_eq!(&json[span.offset..span.offset + span.len], b"12345");
647
648        // ,
649        let t = adapter.next_token().unwrap();
650        assert!(matches!(t.token, Token::Comma));
651
652        // Continue with "keep"
653        let t = adapter.next_token().unwrap();
654        assert_eq!(t.token, Token::String(Cow::Borrowed("keep")));
655    }
656
657    #[test]
658    fn test_skip_object() {
659        let json = br#"{"skip": {"nested": {"deep": true}}, "keep": 1}"#;
660        let mut adapter = SliceAdapter::<true>::new(json);
661
662        // {
663        adapter.next_token().unwrap();
664        // "skip"
665        adapter.next_token().unwrap();
666        // :
667        adapter.next_token().unwrap();
668
669        // Skip the entire nested object
670        let span = adapter.skip().unwrap();
671        assert_eq!(
672            &json[span.offset..span.offset + span.len],
673            br#"{"nested": {"deep": true}}"#
674        );
675
676        // ,
677        let t = adapter.next_token().unwrap();
678        assert!(matches!(t.token, Token::Comma));
679
680        // "keep"
681        let t = adapter.next_token().unwrap();
682        assert_eq!(t.token, Token::String(Cow::Borrowed("keep")));
683    }
684
685    #[test]
686    fn test_skip_array() {
687        let json = br#"{"skip": [1, [2, 3], 4], "keep": true}"#;
688        let mut adapter = SliceAdapter::<true>::new(json);
689
690        // {
691        adapter.next_token().unwrap();
692        // "skip"
693        adapter.next_token().unwrap();
694        // :
695        adapter.next_token().unwrap();
696
697        // Skip the entire array
698        let span = adapter.skip().unwrap();
699        assert_eq!(
700            &json[span.offset..span.offset + span.len],
701            br#"[1, [2, 3], 4]"#
702        );
703
704        // ,
705        adapter.next_token().unwrap();
706
707        // "keep"
708        let t = adapter.next_token().unwrap();
709        assert_eq!(t.token, Token::String(Cow::Borrowed("keep")));
710
711        // :
712        adapter.next_token().unwrap();
713
714        // true
715        let t = adapter.next_token().unwrap();
716        assert!(matches!(t.token, Token::True));
717    }
718
719    #[test]
720    fn test_skip_string_no_allocation() {
721        // When skipping a string, even one with escapes, we shouldn't allocate
722        let json = br#"{"skip": "hello\nworld\twith\rescapes", "keep": 1}"#;
723        let mut adapter = SliceAdapter::<true>::new(json);
724
725        // {
726        adapter.next_token().unwrap();
727        // "skip"
728        adapter.next_token().unwrap();
729        // :
730        adapter.next_token().unwrap();
731
732        // Skip the string - the span covers the whole string including quotes
733        let span = adapter.skip().unwrap();
734        assert_eq!(
735            &json[span.offset..span.offset + span.len],
736            br#""hello\nworld\twith\rescapes""#
737        );
738    }
739
740    #[test]
741    fn test_borrow_false_always_owned() {
742        let json = br#""no escapes here""#;
743        let mut adapter = SliceAdapter::<false>::new(json);
744
745        let t = adapter.next_token().unwrap();
746        // Even without escapes, BORROW=false means Owned
747        assert!(matches!(t.token, Token::String(Cow::Owned(_))));
748    }
749
750    #[test]
751    fn test_borrow_true_borrows_when_possible() {
752        let json = br#""no escapes here""#;
753        let mut adapter = SliceAdapter::<true>::new(json);
754
755        let t = adapter.next_token().unwrap();
756        // No escapes + BORROW=true means Borrowed
757        assert!(matches!(t.token, Token::String(Cow::Borrowed(_))));
758    }
759
760    #[test]
761    fn test_windowed_parsing_long_string() {
762        // Test that strings longer than the chunk size (4 bytes) work correctly
763        // This exercises the NeedMore handling
764        let json = br#""hello world""#; // 13 bytes, much longer than chunk_size=4
765        let mut adapter = SliceAdapter::<true>::new(json);
766
767        let t = adapter.next_token().unwrap();
768        assert_eq!(t.token, Token::String(Cow::Borrowed("hello world")));
769        // Span should cover the entire string including quotes
770        assert_eq!(t.span.offset, 0);
771        assert_eq!(t.span.len, 13);
772    }
773
774    #[test]
775    fn test_windowed_parsing_number_at_eof() {
776        // Test that numbers at EOF are finalized correctly
777        let json = b"-123"; // 4 bytes, exactly chunk_size
778        let mut adapter = SliceAdapter::<true>::new(json);
779
780        let t = adapter.next_token().unwrap();
781        assert_eq!(t.token, Token::I64(-123));
782    }
783
784    #[test]
785    fn test_windowed_parsing_complex_object() {
786        // Test a complex object that spans many chunks
787        let json = br#"{"name": "hello world", "value": 12345, "nested": {"a": 1}}"#;
788        let mut adapter = SliceAdapter::<true>::new(json);
789
790        // {
791        assert!(matches!(
792            adapter.next_token().unwrap().token,
793            Token::ObjectStart
794        ));
795        // "name"
796        assert_eq!(
797            adapter.next_token().unwrap().token,
798            Token::String(Cow::Borrowed("name"))
799        );
800        // :
801        assert!(matches!(adapter.next_token().unwrap().token, Token::Colon));
802        // "hello world"
803        assert_eq!(
804            adapter.next_token().unwrap().token,
805            Token::String(Cow::Borrowed("hello world"))
806        );
807        // ,
808        assert!(matches!(adapter.next_token().unwrap().token, Token::Comma));
809        // "value"
810        assert_eq!(
811            adapter.next_token().unwrap().token,
812            Token::String(Cow::Borrowed("value"))
813        );
814        // :
815        assert!(matches!(adapter.next_token().unwrap().token, Token::Colon));
816        // 12345
817        assert_eq!(adapter.next_token().unwrap().token, Token::U64(12345));
818        // ,
819        assert!(matches!(adapter.next_token().unwrap().token, Token::Comma));
820        // "nested"
821        assert_eq!(
822            adapter.next_token().unwrap().token,
823            Token::String(Cow::Borrowed("nested"))
824        );
825        // :
826        assert!(matches!(adapter.next_token().unwrap().token, Token::Colon));
827        // {
828        assert!(matches!(
829            adapter.next_token().unwrap().token,
830            Token::ObjectStart
831        ));
832        // "a"
833        assert_eq!(
834            adapter.next_token().unwrap().token,
835            Token::String(Cow::Borrowed("a"))
836        );
837        // :
838        assert!(matches!(adapter.next_token().unwrap().token, Token::Colon));
839        // 1
840        assert_eq!(adapter.next_token().unwrap().token, Token::U64(1));
841        // }
842        assert!(matches!(
843            adapter.next_token().unwrap().token,
844            Token::ObjectEnd
845        ));
846        // }
847        assert!(matches!(
848            adapter.next_token().unwrap().token,
849            Token::ObjectEnd
850        ));
851        // EOF
852        assert!(matches!(adapter.next_token().unwrap().token, Token::Eof));
853    }
854}
855
856#[cfg(all(test, feature = "bolero-inline-tests"))]
857#[allow(clippy::while_let_loop, clippy::same_item_push)]
858mod fuzz_tests {
859    use super::*;
860    use bolero::check;
861
862    /// Fuzz the adapter with arbitrary bytes - should never panic
863    #[test]
864    fn fuzz_adapter_arbitrary_bytes() {
865        check!().for_each(|input: &[u8]| {
866            let mut adapter = SliceAdapter::<true>::new(input);
867            loop {
868                match adapter.next_token() {
869                    Ok(spanned) => {
870                        if matches!(spanned.token, Token::Eof) {
871                            break;
872                        }
873                    }
874                    Err(_) => break,
875                }
876            }
877        });
878    }
879
880    /// Fuzz adapter.skip() with arbitrary bytes
881    #[test]
882    fn fuzz_adapter_skip() {
883        check!().for_each(|input: &[u8]| {
884            let mut adapter = SliceAdapter::<true>::new(input);
885            // Try to skip - should handle anything gracefully
886            let _ = adapter.skip();
887        });
888    }
889
890    /// Fuzz with JSON-like objects, alternating next/skip
891    #[test]
892    fn fuzz_adapter_next_skip_alternating() {
893        check!().for_each(|input: &[u8]| {
894            let mut adapter = SliceAdapter::<true>::new(input);
895            let mut use_skip = false;
896            loop {
897                let result = if use_skip {
898                    adapter.skip().map(|_| ())
899                } else {
900                    adapter
901                        .next_token()
902                        .map(|t| if matches!(t.token, Token::Eof) {})
903                };
904                match result {
905                    Ok(()) => {}
906                    Err(_) => break,
907                }
908                use_skip = !use_skip;
909
910                // Safety: check if we're at EOF by peeking
911                let mut peek_adapter = SliceAdapter::<true>::new(&input[adapter.position()..]);
912                if matches!(peek_adapter.next_token(), Ok(t) if matches!(t.token, Token::Eof)) {
913                    break;
914                }
915            }
916        });
917    }
918
919    /// Fuzz BORROW=false path
920    #[test]
921    fn fuzz_adapter_no_borrow() {
922        check!().for_each(|input: &[u8]| {
923            let mut adapter = SliceAdapter::<false>::new(input);
924            loop {
925                match adapter.next_token() {
926                    Ok(spanned) => {
927                        // Verify strings are always Owned when BORROW=false
928                        if let Token::String(cow) = &spanned.token {
929                            assert!(matches!(cow, Cow::Owned(_)));
930                        }
931                        if matches!(spanned.token, Token::Eof) {
932                            break;
933                        }
934                    }
935                    Err(_) => break,
936                }
937            }
938        });
939    }
940
941    /// Fuzz with strings containing various escape sequences
942    #[test]
943    fn fuzz_adapter_string_escapes() {
944        check!().for_each(|content: &[u8]| {
945            // Build {"key": "...content..."}
946            let mut input = Vec::new();
947            input.extend_from_slice(br#"{"k":""#);
948            input.extend_from_slice(content);
949            input.extend_from_slice(br#""}"#);
950
951            let mut adapter = SliceAdapter::<true>::new(&input);
952            loop {
953                match adapter.next_token() {
954                    Ok(t) if matches!(t.token, Token::Eof) => break,
955                    Ok(_) => {}
956                    Err(_) => break,
957                }
958            }
959        });
960    }
961
962    /// Fuzz skip on nested structures
963    #[test]
964    fn fuzz_adapter_skip_nested() {
965        check!().for_each(|input: &[u8]| {
966            // Use first byte as depth indicator
967            let depth = (input.first().copied().unwrap_or(1) as usize % 50) + 1;
968
969            // Build nested array: [[[[...]]]]
970            let mut nested = Vec::new();
971            for _ in 0..depth {
972                nested.push(b'[');
973            }
974            nested.extend_from_slice(b"null");
975            for _ in 0..depth {
976                nested.push(b']');
977            }
978
979            let mut adapter = SliceAdapter::<true>::new(&nested);
980            let result = adapter.skip();
981            assert!(result.is_ok());
982            let span = result.unwrap();
983            assert_eq!(span.len, nested.len());
984        });
985    }
986}