• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kaidokert / picojson-rs / 16297220988

15 Jul 2025 03:13PM UTC coverage: 91.543% (-2.3%) from 93.865%
16297220988

Pull #57

github

web-flow
Merge 8d596fc77 into 1dbca311e
Pull Request #57: Even bigger refactor

472 of 637 new or added lines in 8 files covered. (74.1%)

17 existing lines in 5 files now uncovered.

4741 of 5179 relevant lines covered (91.54%)

738.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.4
/picojson/src/stream_buffer.rs
1
// SPDX-License-Identifier: Apache-2.0
2

3
use crate::ParseError;
4

5
/// Error types for StreamBuffer operations
6
#[derive(Debug, PartialEq)]
7
pub enum StreamBufferError {
8
    /// Buffer is full and cannot accommodate more data
9
    BufferFull,
10
    /// Attempted to read beyond available data
11
    EndOfData,
12
    /// An unexpected error occurred.
13
    Unexpected,
14
    /// Invalid slice bounds provided for string extraction
15
    InvalidSliceBounds,
16
}
17

18
/// StreamBuffer manages a single buffer for both input and escape processing
19
///
20
/// Key design principles:
21
/// - Reader fills unused portions of buffer directly
22
/// - Unescaped content is copied to buffer start when needed
23
/// - Zero-copy string extraction when no escapes are present
24
/// - Guaranteed space for escape processing (unescaped ≤ escaped)
25
pub struct StreamBuffer<'a> {
26
    /// The entire buffer slice
27
    buffer: &'a mut [u8],
28
    /// Current position where tokenizer is reading
29
    tokenize_pos: usize,
30
    /// End of valid data from Reader (buffer[0..data_end] contains valid data)
31
    data_end: usize,
32
    /// Length of unescaped content at buffer start (0 if no unescaping active)
33
    unescaped_len: usize,
34
}
35

36
impl<'a> StreamBuffer<'a> {
37
    /// Panic-free copy_within implementation that handles overlapping ranges
38
    /// Based on memmove behavior but without panic machinery
39
    fn safe_copy_within(&mut self, src_start: usize, src_end: usize, dest: usize) {
619✔
40
        let count = src_end.saturating_sub(src_start);
619✔
41

42
        // Early return if nothing to copy or bounds are invalid
43
        if count == 0
619✔
44
            || src_start >= self.buffer.len()
619✔
45
            || src_end > self.buffer.len()
619✔
46
            || dest >= self.buffer.len()
619✔
47
        {
48
            return;
×
49
        }
619✔
50

51
        // Ensure dest + count doesn't exceed buffer
52
        let max_copy = (self.buffer.len().saturating_sub(dest)).min(count);
619✔
53
        if max_copy == 0 {
619✔
54
            return;
×
55
        }
619✔
56

57
        let iterator: &mut dyn Iterator<Item = usize> = if dest <= src_start {
619✔
58
            &mut (0..max_copy)
619✔
59
        } else {
60
            &mut (0..max_copy).rev()
×
61
        };
62

63
        for i in iterator {
7,271✔
64
            if let (Some(src_byte), Some(dest_slot)) = (
6,652✔
65
                self.buffer.get(src_start.wrapping_add(i)).copied(),
6,652✔
66
                self.buffer.get_mut(dest.wrapping_add(i)),
6,652✔
67
            ) {
6,652✔
68
                *dest_slot = src_byte;
6,652✔
69
            }
6,652✔
70
        }
71
    }
619✔
72
    /// Create a new StreamBuffer with the given buffer slice
73
    pub fn new(buffer: &'a mut [u8]) -> Self {
1,094✔
74
        Self {
1,094✔
75
            buffer,
1,094✔
76
            tokenize_pos: 0,
1,094✔
77
            data_end: 0,
1,094✔
78
            unescaped_len: 0,
1,094✔
79
        }
1,094✔
80
    }
1,094✔
81

82
    /// Get the current byte at tokenize position
83
    pub fn current_byte(&self) -> Result<u8, StreamBufferError> {
18,284✔
84
        if self.tokenize_pos >= self.data_end {
18,284✔
85
            return Err(StreamBufferError::EndOfData);
1✔
86
        }
18,283✔
87
        self.buffer
18,283✔
88
            .get(self.tokenize_pos)
18,283✔
89
            .copied()
18,283✔
90
            .ok_or(StreamBufferError::EndOfData)
18,283✔
91
    }
18,284✔
92

93
    /// Advance the tokenize position by one byte
94
    pub fn advance(&mut self) -> Result<(), StreamBufferError> {
18,313✔
95
        if self.tokenize_pos >= self.data_end {
18,313✔
96
            return Err(StreamBufferError::EndOfData);
1✔
97
        }
18,312✔
98
        self.tokenize_pos = self.tokenize_pos.wrapping_add(1);
18,312✔
99
        Ok(())
18,312✔
100
    }
18,313✔
101

102
    /// Get remaining bytes available for reading
103
    pub fn remaining_bytes(&self) -> usize {
356✔
104
        self.data_end.saturating_sub(self.tokenize_pos)
356✔
105
    }
356✔
106

107
    /// Get slice for Reader to fill with new data
108
    /// Returns None if no space available
109
    pub fn get_fill_slice(&mut self) -> Option<&mut [u8]> {
13,212✔
110
        if self.data_end >= self.buffer.len() {
13,212✔
111
            return None;
1,792✔
112
        }
11,420✔
113
        self.buffer.get_mut(self.data_end..)
11,420✔
114
    }
13,212✔
115

116
    /// Compact buffer by moving unprocessed data from a given start offset to the beginning.
117
    ///
118
    /// # Arguments
119
    /// * `start_offset` - The position from which to preserve data.
120
    ///
121
    /// Returns the offset by which data was moved.
122
    pub fn compact_from(&mut self, start_offset: usize) -> Result<usize, StreamBufferError> {
1,797✔
123
        if start_offset == 0 {
1,797✔
124
            // Already at start, no compaction possible
125
            return Ok(0);
530✔
126
        }
1,267✔
127

128
        let offset = start_offset;
1,267✔
129

130
        if start_offset >= self.data_end {
1,267✔
131
            // All data has been processed, reset to start
132
            self.tokenize_pos = 0;
648✔
133
            self.data_end = 0;
648✔
134
            return Ok(offset);
648✔
135
        }
619✔
136

137
        // Move unprocessed data to start of buffer
138
        let remaining_data = self.data_end.saturating_sub(start_offset);
619✔
139

140
        // Copy existing content if there is any - EXACT same pattern as start_unescaping_with_copy
141
        if self.data_end > start_offset {
619✔
142
            let span_len = remaining_data;
619✔
143

144
            // Ensure the span fits in the buffer - return error instead of silent truncation
145
            if span_len > self.buffer.len() {
619✔
146
                return Err(StreamBufferError::BufferFull);
×
147
            }
619✔
148

149
            let src_range_end = start_offset
619✔
150
                .checked_add(span_len)
619✔
151
                .ok_or(StreamBufferError::InvalidSliceBounds)?;
619✔
152

153
            if src_range_end > self.buffer.len() {
619✔
UNCOV
154
                return Err(StreamBufferError::InvalidSliceBounds);
×
155
            }
619✔
156
            let src_range = start_offset..src_range_end;
619✔
157

158
            // Copy within the same buffer: move data from [start_offset..end] to [0..span_len]
159
            // Use our panic-free copy implementation
160
            self.safe_copy_within(src_range.start, src_range.end, 0);
619✔
161
        }
×
162

163
        // Update positions
164
        self.tokenize_pos = self.tokenize_pos.saturating_sub(offset);
619✔
165
        self.data_end = remaining_data;
619✔
166

167
        Ok(offset)
619✔
168
    }
1,797✔
169

170
    /// Mark that Reader filled `bytes_read` bytes
171
    pub fn mark_filled(&mut self, bytes_read: usize) -> Result<(), StreamBufferError> {
6,346✔
172
        let new_data_end = self.data_end.wrapping_add(bytes_read);
6,346✔
173
        if new_data_end > self.buffer.len() {
6,346✔
174
            return Err(StreamBufferError::Unexpected);
×
175
        }
6,346✔
176
        self.data_end = new_data_end;
6,346✔
177
        Ok(())
6,346✔
178
    }
6,346✔
179

180
    /// Start unescaping and copy existing content from a range in the buffer
181
    /// This handles the common case of starting escape processing partway through a string
182
    pub fn start_unescaping_with_copy(
347✔
183
        &mut self,
347✔
184
        max_escaped_len: usize,
347✔
185
        copy_start: usize,
347✔
186
        copy_end: usize,
347✔
187
    ) -> Result<(), StreamBufferError> {
347✔
188
        // Clear any previous unescaped content
189
        self.unescaped_len = 0;
347✔
190

191
        // Ensure we have space at the start for unescaping
192
        if max_escaped_len > self.buffer.len() {
347✔
193
            return Err(StreamBufferError::BufferFull);
×
194
        }
347✔
195

196
        // Copy existing content if there is any
197
        if copy_end > copy_start && copy_start < self.data_end {
347✔
198
            let span_len = copy_end.saturating_sub(copy_start);
272✔
199

200
            // Ensure the span fits in the buffer - return error instead of silent truncation
201
            if span_len > self.buffer.len() {
272✔
202
                return Err(StreamBufferError::BufferFull);
1✔
203
            }
271✔
204

205
            let src_range = copy_start..copy_start.wrapping_add(span_len);
271✔
206
            if src_range.end > self.buffer.len() {
271✔
207
                return Err(StreamBufferError::InvalidSliceBounds);
×
208
            }
271✔
209

210
            // Copy within the same buffer: move data from [copy_start..copy_end] to [0..span_len]
211
            // Use copy_within to handle overlapping ranges safely
212
            self.buffer.copy_within(src_range, 0);
271✔
213
            self.unescaped_len = span_len;
271✔
214
        }
75✔
215

216
        Ok(())
346✔
217
    }
347✔
218

219
    /// Get the unescaped content slice
220
    pub fn get_unescaped_slice(&self) -> Result<&[u8], StreamBufferError> {
181✔
221
        if self.unescaped_len == 0 {
181✔
222
            return Err(StreamBufferError::InvalidSliceBounds);
1✔
223
        }
180✔
224
        self.buffer
180✔
225
            .get(0..self.unescaped_len)
180✔
226
            .ok_or(StreamBufferError::Unexpected)
180✔
227
    }
181✔
228

229
    /// Clear unescaped content (call after yielding unescaped string)
230
    pub fn clear_unescaped(&mut self) {
150✔
231
        self.unescaped_len = 0;
150✔
232
    }
150✔
233

234
    /// Get current tokenize position (for string start tracking)
235
    pub fn current_position(&self) -> usize {
21,218✔
236
        self.tokenize_pos
21,218✔
237
    }
21,218✔
238

239
    /// Check if buffer is empty (no more data to process)
240
    pub fn is_empty(&self) -> bool {
38,044✔
241
        self.tokenize_pos >= self.data_end
38,044✔
242
    }
38,044✔
243

244
    /// Check if we have unescaped content ready
245
    pub fn has_unescaped_content(&self) -> bool {
7,950✔
246
        self.unescaped_len > 0
7,950✔
247
    }
7,950✔
248

249
    /// Append a single byte to the unescaped content
250
    pub fn append_unescaped_byte(&mut self, byte: u8) -> Result<(), StreamBufferError> {
1,422✔
251
        if let Some(b) = self.buffer.get_mut(self.unescaped_len) {
1,422✔
252
            *b = byte;
1,421✔
253
            self.unescaped_len = self.unescaped_len.wrapping_add(1);
1,421✔
254
            Ok(())
1,421✔
255
        } else {
256
            Err(StreamBufferError::BufferFull)
1✔
257
        }
258
    }
1,422✔
259

260
    /// Get a string slice from the buffer (zero-copy)
261
    /// Used for strings without escapes
262
    pub fn get_string_slice(&self, start: usize, end: usize) -> Result<&[u8], StreamBufferError> {
871✔
263
        if start > end || end > self.data_end {
871✔
264
            return Err(StreamBufferError::InvalidSliceBounds);
×
265
        }
871✔
266
        self.buffer
871✔
267
            .get(start..end)
871✔
268
            .ok_or(StreamBufferError::InvalidSliceBounds)
871✔
269
    }
871✔
270
}
271

272
#[cfg(test)]
273
mod tests {
274
    use super::*;
275

276
    #[test]
277
    fn test_lifetime_expectations() {
1✔
278
        // This test demonstrates how StreamBuffer lifetimes should work
279
        let mut buffer = [0u8; 100];
1✔
280
        let mut stream_buffer = StreamBuffer::new(&mut buffer);
1✔
281

282
        // Simulate some data being in the buffer
283
        let test_data = b"hello world";
1✔
284
        stream_buffer.buffer[0..test_data.len()].copy_from_slice(test_data);
1✔
285
        stream_buffer.data_end = test_data.len();
1✔
286

287
        // Test that we can get buffer data
288

289
        // Test unescaped content - add some unescaped data
290
        stream_buffer.unescaped_len = 3;
1✔
291
        stream_buffer.buffer[0..3].copy_from_slice(b"abc");
1✔
292

293
        let unescaped_slice = stream_buffer.get_unescaped_slice().unwrap();
1✔
294
        assert_eq!(unescaped_slice, b"abc");
1✔
295

296
        // The key expectation: these slices should live as long as the original buffer
297
        // and be usable to create String::Borrowed(&'buffer str) and String::Unescaped(&'buffer str)
298
    }
1✔
299

300
    #[test]
301
    fn test_new_stream_buffer() {
1✔
302
        let mut buffer = [0u8; 100];
1✔
303
        let db = StreamBuffer::new(&mut buffer);
1✔
304

305
        assert_eq!(db.tokenize_pos, 0);
1✔
306
        assert_eq!(db.data_end, 0);
1✔
307
        assert_eq!(db.unescaped_len, 0);
1✔
308
        assert!(db.is_empty());
1✔
309
    }
1✔
310

311
    #[test]
312
    fn test_fill_and_advance() {
1✔
313
        let mut buffer = [0u8; 100];
1✔
314
        let mut db = StreamBuffer::new(&mut buffer);
1✔
315

316
        // Fill with some data
317
        {
1✔
318
            let fill_slice = db.get_fill_slice().unwrap();
1✔
319
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
320
        }
1✔
321
        db.mark_filled(5).unwrap();
1✔
322

323
        assert_eq!(db.data_end, 5);
1✔
324
        assert_eq!(db.remaining_bytes(), 5);
1✔
325

326
        // Read bytes
327
        assert_eq!(db.current_byte().unwrap(), b'h');
1✔
328
        db.advance().unwrap();
1✔
329
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
330
        assert_eq!(db.remaining_bytes(), 4);
1✔
331
    }
1✔
332

333
    #[test]
334
    fn test_error_conditions() {
1✔
335
        let mut buffer = [0u8; 10];
1✔
336
        let mut db = StreamBuffer::new(&mut buffer);
1✔
337

338
        // EndOfData errors
339
        assert_eq!(db.current_byte().unwrap_err(), StreamBufferError::EndOfData);
1✔
340
        assert_eq!(db.advance().unwrap_err(), StreamBufferError::EndOfData);
1✔
341

342
        // No unescaped content
343
        assert!(db.get_unescaped_slice().is_err());
1✔
344
    }
1✔
345

346
    #[test]
347
    fn test_buffer_full_scenario() {
1✔
348
        // Test what happens when buffer gets completely full
349
        let mut buffer = [0u8; 10];
1✔
350
        let mut db = StreamBuffer::new(&mut buffer);
1✔
351

352
        // Fill buffer completely
353
        {
1✔
354
            let fill_slice = db.get_fill_slice().unwrap();
1✔
355
            fill_slice.copy_from_slice(b"0123456789");
1✔
356
        }
1✔
357
        db.mark_filled(10).unwrap();
1✔
358

359
        // No more space for filling
360
        assert!(db.get_fill_slice().is_none());
1✔
361

362
        // We can still read from buffer
363
        assert_eq!(db.current_byte().unwrap(), b'0');
1✔
364
        assert_eq!(db.remaining_bytes(), 10);
1✔
365
    }
1✔
366

367
    #[test]
368
    fn test_minimal_buffer_with_long_token() {
1✔
369
        // Test very small buffer with a token that doesn't fit
370
        let mut buffer = [0u8; 8]; // Very small buffer
1✔
371
        let mut db = StreamBuffer::new(&mut buffer);
1✔
372

373
        // Try to put a string that's almost as big as the buffer
374
        {
1✔
375
            let fill_slice = db.get_fill_slice().unwrap();
1✔
376
            fill_slice[0..6].copy_from_slice(b"\"hello"); // Start of a long string, no closing quote
1✔
377
        }
1✔
378
        db.mark_filled(6).unwrap();
1✔
379

380
        // Advance through the data
381
        for _ in 0..6 {
7✔
382
            db.advance().unwrap();
6✔
383
        }
6✔
384

385
        // Now buffer is exhausted but we don't have a complete token
386
        assert!(db.is_empty());
1✔
387
        assert_eq!(db.remaining_bytes(), 0);
1✔
388

389
        // This simulates the scenario where we need more data but can't fit it
390
        // The parser would need to handle this by buffering the incomplete token
391
    }
1✔
392

393
    #[test]
394
    fn test_reader_returns_zero_bytes() {
1✔
395
        let mut buffer = [0u8; 20];
1✔
396
        let mut db = StreamBuffer::new(&mut buffer);
1✔
397

398
        // Simulate Reader returning 0 bytes (EOF)
399
        {
400
            let fill_slice = db.get_fill_slice().unwrap();
1✔
401
            assert_eq!(fill_slice.len(), 20);
1✔
402
            // Reader returns 0 bytes - simulating EOF or no data available
403
        }
404
        db.mark_filled(0).unwrap(); // Reader returned 0
1✔
405

406
        assert!(db.is_empty());
1✔
407
        assert_eq!(db.data_end, 0);
1✔
408
        assert_eq!(db.remaining_bytes(), 0);
1✔
409

410
        // Should still be able to get fill slice for next attempt
411
        let fill_slice = db.get_fill_slice().unwrap();
1✔
412
        assert_eq!(fill_slice.len(), 20);
1✔
413
    }
1✔
414

415
    #[test]
416
    fn test_boundary_conditions() {
1✔
417
        let mut buffer = [0u8; 3]; // Absolute minimum
1✔
418
        let mut db = StreamBuffer::new(&mut buffer);
1✔
419

420
        // Can't even hold a proper JSON token, but should not crash
421
        {
1✔
422
            let fill_slice = db.get_fill_slice().unwrap();
1✔
423
            fill_slice.copy_from_slice(b"\"a\"");
1✔
424
        }
1✔
425
        db.mark_filled(3).unwrap();
1✔
426

427
        // Should be able to read through it
428
        assert_eq!(db.current_byte().unwrap(), b'"');
1✔
429
        db.advance().unwrap();
1✔
430
        assert_eq!(db.current_byte().unwrap(), b'a');
1✔
431
        db.advance().unwrap();
1✔
432
        assert_eq!(db.current_byte().unwrap(), b'"');
1✔
433
        db.advance().unwrap();
1✔
434

435
        assert!(db.is_empty());
1✔
436
    }
1✔
437

438
    #[test]
439
    fn test_start_unescaping_with_copy_span_too_large() {
1✔
440
        let mut buffer = [0u8; 10]; // Small buffer
1✔
441
        let mut db = StreamBuffer::new(&mut buffer);
1✔
442

443
        // Fill buffer with some data
444
        {
1✔
445
            let fill_slice = db.get_fill_slice().unwrap();
1✔
446
            fill_slice.copy_from_slice(b"0123456789");
1✔
447
        }
1✔
448
        db.mark_filled(10).unwrap();
1✔
449

450
        // Try to copy a span that's larger than the entire buffer
451
        let copy_start = 0;
1✔
452
        let copy_end = 15; // This span (15 bytes) is larger than buffer (10 bytes)
1✔
453
        let max_escaped_len = 5; // This is fine
1✔
454

455
        // Should return BufferFull error instead of silently truncating
456
        let result = db.start_unescaping_with_copy(max_escaped_len, copy_start, copy_end);
1✔
457
        assert_eq!(result.unwrap_err(), StreamBufferError::BufferFull);
1✔
458

459
        // Test boundary case: span exactly equals buffer size should work
460
        let copy_end_exact = 10; // Span of exactly 10 bytes (buffer size)
1✔
461
        let result = db.start_unescaping_with_copy(max_escaped_len, 0, copy_end_exact);
1✔
462
        assert!(result.is_ok());
1✔
463
        assert_eq!(db.unescaped_len, 10);
1✔
464

465
        // Test valid smaller span should work
466
        db.clear_unescaped();
1✔
467
        let result = db.start_unescaping_with_copy(max_escaped_len, 2, 6); // 4 byte span
1✔
468
        assert!(result.is_ok());
1✔
469
        assert_eq!(db.unescaped_len, 4);
1✔
470
        assert_eq!(db.get_unescaped_slice().unwrap(), b"2345");
1✔
471
    }
1✔
472

473
    #[test]
474
    fn test_append_unescaped_byte_uses_full_buffer() {
1✔
475
        let mut buffer = [0u8; 10]; // 10 byte buffer
1✔
476
        let mut db = StreamBuffer::new(&mut buffer);
1✔
477

478
        // Should be able to append up to buffer_len bytes (no more escape reserve!)
479
        for i in 0..10 {
11✔
480
            let result = db.append_unescaped_byte(b'A');
10✔
481
            assert!(result.is_ok(), "Failed at byte {}", i);
10✔
482
        }
483

484
        assert_eq!(db.unescaped_len, 10);
1✔
485

486
        // One more byte should fail because buffer is full
487
        let result = db.append_unescaped_byte(b'B');
1✔
488
        assert_eq!(result.unwrap_err(), StreamBufferError::BufferFull);
1✔
489
    }
1✔
490

491
    #[test]
492
    fn test_compact_basic() {
1✔
493
        let mut buffer = [0u8; 10];
1✔
494
        let mut db = StreamBuffer::new(&mut buffer);
1✔
495

496
        // Fill buffer with data: "0123456789"
497
        {
1✔
498
            let fill_slice = db.get_fill_slice().unwrap();
1✔
499
            fill_slice.copy_from_slice(b"0123456789");
1✔
500
        }
1✔
501
        db.mark_filled(10).unwrap();
1✔
502

503
        // Process some data (advance tokenize_pos to position 4)
504
        for _ in 0..4 {
5✔
505
            db.advance().unwrap();
4✔
506
        }
4✔
507

508
        // Before compact: tokenize_pos=4, data_end=10, remaining="456789"
509
        assert_eq!(db.tokenize_pos, 4);
1✔
510
        assert_eq!(db.data_end, 10);
1✔
511
        assert_eq!(db.remaining_bytes(), 6);
1✔
512

513
        // Compact the buffer
514
        let offset = db.compact_from(4).unwrap();
1✔
515
        assert_eq!(offset, 4); // Data was moved by 4 positions
1✔
516

517
        // After compact: tokenize_pos=0, data_end=6, buffer starts with "456789"
518
        assert_eq!(db.tokenize_pos, 0);
1✔
519
        assert_eq!(db.data_end, 6);
1✔
520
        assert_eq!(db.remaining_bytes(), 6);
1✔
521

522
        // Verify the data was moved correctly
523
        assert_eq!(db.current_byte().unwrap(), b'4');
1✔
524
        db.advance().unwrap();
1✔
525
        assert_eq!(db.current_byte().unwrap(), b'5');
1✔
526
        db.advance().unwrap();
1✔
527
        assert_eq!(db.current_byte().unwrap(), b'6');
1✔
528
    }
1✔
529

530
    #[test]
531
    fn test_compact_from_preserves_number() {
1✔
532
        let mut buffer = [0u8; 10];
1✔
533
        let mut db = StreamBuffer::new(&mut buffer);
1✔
534
        db.buffer.copy_from_slice(b"0123456789");
1✔
535
        db.data_end = 10;
1✔
536
        db.tokenize_pos = 5;
1✔
537
        let number_start_pos = 3;
1✔
538

539
        let offset = db.compact_from(number_start_pos).unwrap();
1✔
540
        assert_eq!(offset, 3);
1✔
541
        assert_eq!(db.tokenize_pos, 2); // 5 - 3
1✔
542
        assert_eq!(db.data_end, 7); // 10 - 3
1✔
543
        assert_eq!(&db.buffer[..db.data_end], b"3456789");
1✔
544
    }
1✔
545

546
    #[test]
547
    fn test_compact_no_op_when_at_start() {
1✔
548
        let mut buffer = [0u8; 10];
1✔
549
        let mut db = StreamBuffer::new(&mut buffer);
1✔
550

551
        // Fill buffer with data
552
        {
1✔
553
            let fill_slice = db.get_fill_slice().unwrap();
1✔
554
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
555
        }
1✔
556
        db.mark_filled(5).unwrap();
1✔
557

558
        // Don't advance tokenize_pos (stays at 0)
559
        assert_eq!(db.tokenize_pos, 0);
1✔
560
        assert_eq!(db.data_end, 5);
1✔
561

562
        // Compact should be no-op
563
        let offset = db.compact_from(0).unwrap();
1✔
564
        assert_eq!(offset, 0); // No movement occurred
1✔
565

566
        // Should be unchanged
567
        assert_eq!(db.tokenize_pos, 0);
1✔
568
        assert_eq!(db.data_end, 5);
1✔
569
        assert_eq!(db.current_byte().unwrap(), b'h');
1✔
570
    }
1✔
571

572
    #[test]
573
    fn test_compact_all_data_processed() {
1✔
574
        let mut buffer = [0u8; 10];
1✔
575
        let mut db = StreamBuffer::new(&mut buffer);
1✔
576

577
        // Fill buffer with data
578
        {
1✔
579
            let fill_slice = db.get_fill_slice().unwrap();
1✔
580
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
581
        }
1✔
582
        db.mark_filled(5).unwrap();
1✔
583

584
        // Process all data
585
        for _ in 0..5 {
6✔
586
            db.advance().unwrap();
5✔
587
        }
5✔
588

589
        // All data processed
590
        assert_eq!(db.tokenize_pos, 5);
1✔
591
        assert_eq!(db.data_end, 5);
1✔
592
        assert!(db.is_empty());
1✔
593

594
        // Compact should reset to start
595
        let offset = db.compact_from(5).unwrap();
1✔
596
        assert_eq!(offset, 5); // All data was processed, moved by 5
1✔
597

598
        // Should be reset to empty state
599
        assert_eq!(db.tokenize_pos, 0);
1✔
600
        assert_eq!(db.data_end, 0);
1✔
601
        assert!(db.is_empty());
1✔
602
    }
1✔
603

604
    #[test]
605
    fn test_compact_enables_new_data_fill() {
1✔
606
        let mut buffer = [0u8; 10];
1✔
607
        let mut db = StreamBuffer::new(&mut buffer);
1✔
608

609
        // Fill buffer completely
610
        {
1✔
611
            let fill_slice = db.get_fill_slice().unwrap();
1✔
612
            fill_slice.copy_from_slice(b"0123456789");
1✔
613
        }
1✔
614
        db.mark_filled(10).unwrap();
1✔
615

616
        // Process half the data
617
        for _ in 0..5 {
6✔
618
            db.advance().unwrap();
5✔
619
        }
5✔
620

621
        // Buffer is full, can't get fill slice
622
        assert!(db.get_fill_slice().is_none());
1✔
623

624
        // Compact to make space
625
        let offset = db.compact_from(5).unwrap();
1✔
626
        assert_eq!(offset, 5); // Data moved by 5 positions
1✔
627

628
        // Now should be able to get fill slice again
629
        let fill_slice = db.get_fill_slice().unwrap();
1✔
630
        assert_eq!(fill_slice.len(), 5); // 5 bytes available (10 - 5 remaining)
1✔
631

632
        // Fill with new data
633
        fill_slice[0..5].copy_from_slice(b"ABCDE");
1✔
634
        db.mark_filled(5).unwrap();
1✔
635

636
        // Verify combined data: "56789ABCDE"
637
        assert_eq!(db.data_end, 10);
1✔
638
        assert_eq!(db.current_byte().unwrap(), b'5');
1✔
639
        db.advance().unwrap();
1✔
640
        assert_eq!(db.current_byte().unwrap(), b'6');
1✔
641
        db.advance().unwrap();
1✔
642
        assert_eq!(db.current_byte().unwrap(), b'7');
1✔
643
        db.advance().unwrap();
1✔
644
        assert_eq!(db.current_byte().unwrap(), b'8');
1✔
645
        db.advance().unwrap();
1✔
646
        assert_eq!(db.current_byte().unwrap(), b'9');
1✔
647
        db.advance().unwrap();
1✔
648
        assert_eq!(db.current_byte().unwrap(), b'A');
1✔
649
    }
1✔
650

651
    #[test]
652
    fn test_compact_with_single_byte_remaining() {
1✔
653
        let mut buffer = [0u8; 5];
1✔
654
        let mut db = StreamBuffer::new(&mut buffer);
1✔
655

656
        // Fill buffer: "abcde"
657
        {
1✔
658
            let fill_slice = db.get_fill_slice().unwrap();
1✔
659
            fill_slice.copy_from_slice(b"abcde");
1✔
660
        }
1✔
661
        db.mark_filled(5).unwrap();
1✔
662

663
        // Process almost all data (leave one byte)
664
        for _ in 0..4 {
5✔
665
            db.advance().unwrap();
4✔
666
        }
4✔
667

668
        // One byte remaining
669
        assert_eq!(db.remaining_bytes(), 1);
1✔
670
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
671

672
        // Compact
673
        let offset = db.compact_from(4).unwrap();
1✔
674
        assert_eq!(offset, 4); // Moved by 4 positions
1✔
675

676
        // Should have moved the last byte to start
677
        assert_eq!(db.tokenize_pos, 0);
1✔
678
        assert_eq!(db.data_end, 1);
1✔
679
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
680
        assert_eq!(db.remaining_bytes(), 1);
1✔
681

682
        // Should have space for 4 more bytes
683
        let fill_slice = db.get_fill_slice().unwrap();
1✔
684
        assert_eq!(fill_slice.len(), 4);
1✔
685
    }
1✔
686

687
    #[test]
688
    fn test_compact_buffer_wall_scenario() {
1✔
689
        // Simulate hitting the buffer wall during token processing
690
        // This tests the "always compact when buffer full" strategy
691

692
        let mut buffer = [0u8; 10];
1✔
693
        let mut db = StreamBuffer::new(&mut buffer);
1✔
694

695
        // Fill buffer completely with: `{"hello_wo` (10 bytes, fills buffer exactly)
696
        {
1✔
697
            let fill_slice = db.get_fill_slice().unwrap();
1✔
698
            fill_slice.copy_from_slice(b"{\"hello_wo");
1✔
699
        }
1✔
700
        db.mark_filled(10).unwrap();
1✔
701

702
        // Process tokens: { " h e l l o _ w o
703
        // Parser is in State::String(2) tracking string start at position 2
704
        let mut _string_start_pos = 2; // Parser's state: string started at pos 2
1✔
705

706
        // Advance to simulate tokenizer processing
707
        for _ in 0..10 {
11✔
708
            db.advance().unwrap();
10✔
709
        }
10✔
710

711
        // Buffer is now empty, we hit the wall
712
        assert!(db.is_empty());
1✔
713
        assert!(db.get_fill_slice().is_none()); // No space to read more
1✔
714

715
        // ALWAYS compact when hitting buffer wall
716
        let offset = db.compact_from(10).unwrap();
1✔
717
        assert_eq!(offset, 10); // Moved by 10 positions (everything was processed)
1✔
718

719
        // Parser updates state: string_start_pos = 2 - 10 = -8
720
        // Since string_start_pos < 0, the original string start was discarded!
721
        // Parser must now switch to escape/copy mode for the continuation
722
        if _string_start_pos < offset {
1✔
723
            // Original string start was discarded - must use escape/copy mode
1✔
724
            // In real implementation, parser would copy what it had processed to unescaped buffer
1✔
725
            _string_start_pos = 0; // Reset for escape mode
1✔
726
        } else {
1✔
727
            _string_start_pos = _string_start_pos.saturating_sub(offset); // Normal position update
×
728
        }
×
729

730
        // After compaction, buffer is reset and ready for new data
731
        assert_eq!(db.tokenize_pos, 0);
1✔
732
        assert_eq!(db.data_end, 0);
1✔
733

734
        // Now we can read more data
735
        {
736
            let fill_slice = db.get_fill_slice().unwrap();
1✔
737
            assert_eq!(fill_slice.len(), 10); // Full buffer available
1✔
738
            fill_slice[0..3].copy_from_slice(b"rld");
1✔
739
        }
740
        db.mark_filled(3).unwrap();
1✔
741

742
        // Continue processing the string continuation
743
        assert_eq!(db.current_byte().unwrap(), b'r');
1✔
744
        assert_eq!(db.remaining_bytes(), 3);
1✔
745
    }
1✔
746

747
    #[test]
748
    fn test_compact_saves_partial_token() {
1✔
749
        // Test case where compaction saves partial token at end of buffer
750
        let mut buffer = [0u8; 8];
1✔
751
        let mut db = StreamBuffer::new(&mut buffer);
1✔
752

753
        // Fill buffer: {"hel|lo"} where we process up to 'l' and hit wall with "lo\"}" remaining
754
        {
1✔
755
            let fill_slice = db.get_fill_slice().unwrap();
1✔
756
            fill_slice.copy_from_slice(b"{\"hello\"");
1✔
757
        }
1✔
758
        db.mark_filled(8).unwrap();
1✔
759

760
        // Process: { " h e l - stop here with "lo\"" remaining
761
        for _ in 0..5 {
6✔
762
            db.advance().unwrap();
5✔
763
        }
5✔
764

765
        // Current state: parser at position 5, with "lo\"" remaining (3 bytes)
766
        let mut _string_start_pos = 2; // Parser state: string started at position 2
1✔
767
        assert_eq!(db.current_byte().unwrap(), b'l');
1✔
768
        assert_eq!(db.remaining_bytes(), 3);
1✔
769

770
        // Hit buffer wall, compact
771
        let offset = db.compact_from(5).unwrap();
1✔
772
        assert_eq!(offset, 5); // Moved data by 5 positions
1✔
773

774
        // Update parser state
775
        _string_start_pos = if _string_start_pos < offset {
1✔
776
            0 // Switch to escape mode - original start was discarded
1✔
777
        } else {
778
            _string_start_pos - offset // Normal position update: 2 - 5 = -3, so switch to escape mode
×
779
        };
780

781
        // After compaction: "lo\"" is now at start of buffer
782
        assert_eq!(db.tokenize_pos, 0);
1✔
783
        assert_eq!(db.data_end, 3);
1✔
784
        assert_eq!(db.current_byte().unwrap(), b'l');
1✔
785
        assert_eq!(db.remaining_bytes(), 3);
1✔
786

787
        // We saved 3 bytes, gained 5 bytes of space
788
        let fill_slice = db.get_fill_slice().unwrap();
1✔
789
        assert_eq!(fill_slice.len(), 5);
1✔
790
    }
1✔
791

792
    #[test]
793
    fn test_position_update_after_compaction_normal_case() {
1✔
794
        // Test normal position updates where positions are preserved
795

796
        // Case 1: String position preserved after compaction
797
        let _state = crate::shared::State::String(10);
1✔
798
        let offset = 5;
1✔
799

800
        // Simulate the position update logic
801
        let updated_pos = if 10 < offset {
1✔
802
            0 // Would need escape mode
×
803
        } else {
804
            10 - offset // Normal position update: 10 - 5 = 5
1✔
805
        };
806

807
        assert_eq!(updated_pos, 5);
1✔
808

809
        // Case 2: Key position preserved after compaction
810
        let key_pos = 8;
1✔
811
        let offset = 3;
1✔
812

813
        let updated_key_pos = if key_pos < offset {
1✔
814
            0 // Would need escape mode
×
815
        } else {
816
            key_pos - offset // Normal position update: 8 - 3 = 5
1✔
817
        };
818

819
        assert_eq!(updated_key_pos, 5);
1✔
820

821
        // Case 3: Number position preserved after compaction
822
        let number_pos = 15;
1✔
823
        let offset = 7;
1✔
824

825
        let updated_number_pos = if number_pos < offset {
1✔
826
            // Numbers should not normally lose their start position
827
            panic!("Number position discarded - buffer too small");
×
828
        } else {
829
            number_pos - offset // Normal position update: 15 - 7 = 8
1✔
830
        };
831

832
        assert_eq!(updated_number_pos, 8);
1✔
833
    }
1✔
834

835
    #[test]
836
    fn test_position_update_after_compaction_escape_mode_case() {
1✔
837
        // Test position updates where original positions are discarded (need escape mode)
838

839
        // Case 1: String position discarded - needs escape mode
840
        let string_pos = 3;
1✔
841
        let offset = 7; // Offset is larger than string position
1✔
842

843
        let needs_escape_mode = string_pos < offset;
1✔
844
        assert!(needs_escape_mode);
1✔
845

846
        let updated_string_pos = if needs_escape_mode {
1✔
847
            0 // Reset for escape mode
1✔
848
        } else {
849
            string_pos - offset
×
850
        };
851

852
        assert_eq!(updated_string_pos, 0);
1✔
853

854
        // Case 2: Key position discarded - needs escape mode
855
        let key_pos = 2;
1✔
856
        let offset = 8;
1✔
857

858
        let needs_escape_mode = key_pos < offset;
1✔
859
        assert!(needs_escape_mode);
1✔
860

861
        let updated_key_pos = if needs_escape_mode {
1✔
862
            0 // Reset for escape mode
1✔
863
        } else {
864
            key_pos - offset
×
865
        };
866

867
        assert_eq!(updated_key_pos, 0);
1✔
868

869
        // Case 3: Number position discarded - should be an error
870
        let number_pos = 1;
1✔
871
        let offset = 5;
1✔
872

873
        let should_error = number_pos < offset;
1✔
874
        assert!(should_error); // Numbers spanning compaction boundaries should error
1✔
875
    }
1✔
876

877
    #[test]
878
    fn test_position_update_boundary_conditions() {
1✔
879
        // Test exact boundary conditions for position updates
880

881
        // Case 1: Position exactly equals offset
882
        let pos = 5;
1✔
883
        let offset = 5;
1✔
884

885
        let needs_escape_mode = pos < offset; // false, pos == offset
1✔
886
        assert!(!needs_escape_mode);
1✔
887

888
        let updated_pos = pos - offset; // 5 - 5 = 0
1✔
889
        assert_eq!(updated_pos, 0);
1✔
890

891
        // Case 2: Position one less than offset (boundary case)
892
        let pos = 4;
1✔
893
        let offset = 5;
1✔
894

895
        let needs_escape_mode = pos < offset; // true, pos < offset
1✔
896
        assert!(needs_escape_mode);
1✔
897

898
        // Case 3: Position one more than offset (boundary case)
899
        let pos = 6;
1✔
900
        let offset = 5;
1✔
901

902
        let needs_escape_mode = pos < offset; // false, pos > offset
1✔
903
        assert!(!needs_escape_mode);
1✔
904

905
        let updated_pos = pos - offset; // 6 - 5 = 1
1✔
906
        assert_eq!(updated_pos, 1);
1✔
907

908
        // Case 4: Zero offset (no compaction occurred)
909
        let pos = 10;
1✔
910
        let offset = 0;
1✔
911

912
        let needs_escape_mode = pos < offset; // false, 10 < 0
1✔
913
        assert!(!needs_escape_mode);
1✔
914

915
        let updated_pos = pos - offset; // 10 - 0 = 10 (unchanged)
1✔
916
        assert_eq!(updated_pos, 10);
1✔
917
    }
1✔
918

919
    #[test]
920
    fn test_position_update_state_transitions() {
1✔
921
        // Test the complete state transition logic for different parser states
922

923
        // Mock the State enum variants and position update logic
924
        use crate::shared::State;
925

926
        // Case 1: State::None - no position to update
927
        let state = State::None;
1✔
928
        // No position updates needed for None state
929
        match state {
1✔
930
            State::None => {
1✔
931
                // No action needed - test passes
1✔
932
            }
1✔
933
            _ => panic!("Expected State::None"),
×
934
        }
935

936
        // Case 2: String state position updates
937
        let mut string_state = State::String(12);
1✔
938
        let offset = 8;
1✔
939

940
        match &mut string_state {
1✔
941
            State::String(pos) => {
1✔
942
                if *pos < offset {
1✔
943
                    // Would need escape mode
×
944
                    *pos = 0;
×
945
                } else {
1✔
946
                    *pos = pos.saturating_sub(offset); // 12 - 8 = 4
1✔
947
                }
1✔
948
            }
949
            _ => panic!("Expected State::String"),
×
950
        }
951

952
        match string_state {
1✔
953
            State::String(pos) => assert_eq!(pos, 4),
1✔
954
            _ => panic!("Expected State::String"),
×
955
        }
956

957
        // Case 3: Key state needing escape mode
958
        let mut key_state = State::Key(3);
1✔
959
        let offset = 10;
1✔
960

961
        match &mut key_state {
1✔
962
            State::Key(pos) => {
1✔
963
                if *pos < offset {
1✔
964
                    // Needs escape mode
1✔
965
                    *pos = 0;
1✔
966
                } else {
1✔
967
                    *pos = pos.saturating_sub(offset);
×
968
                }
×
969
            }
970
            _ => panic!("Expected State::Key"),
×
971
        }
972

973
        match key_state {
1✔
974
            State::Key(pos) => assert_eq!(pos, 0), // Reset for escape mode
1✔
975
            _ => panic!("Expected State::Key"),
×
976
        }
977

978
        // Case 4: Number state normal update
979
        let mut number_state = State::Number(20);
1✔
980
        let offset = 6;
1✔
981

982
        match &mut number_state {
1✔
983
            State::Number(pos) => {
1✔
984
                if *pos < offset {
1✔
985
                    // This should not happen for numbers in normal operation
986
                    panic!("Number position discarded - buffer too small");
×
987
                } else {
1✔
988
                    *pos = pos.saturating_sub(offset); // 20 - 6 = 14
1✔
989
                }
1✔
990
            }
991
            _ => panic!("Expected State::Number"),
×
992
        }
993

994
        match number_state {
1✔
995
            State::Number(pos) => assert_eq!(pos, 14),
1✔
996
            _ => panic!("Expected State::Number"),
×
997
        }
998
    }
1✔
999
}
1000

1001
impl crate::number_parser::NumberExtractor for StreamBuffer<'_> {
1002
    fn get_number_slice(&self, start: usize, end: usize) -> Result<&[u8], ParseError> {
250✔
1003
        self.get_string_slice(start, end).map_err(Into::into)
250✔
1004
    }
250✔
1005

1006
    fn current_position(&self) -> usize {
250✔
1007
        self.tokenize_pos
250✔
1008
    }
250✔
1009

1010
    fn is_empty(&self) -> bool {
×
1011
        self.tokenize_pos >= self.data_end
×
1012
    }
×
1013
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc