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

kaidokert / picojson-rs / 16259755145

14 Jul 2025 06:34AM UTC coverage: 90.098% (-3.7%) from 93.785%
16259755145

Pull #56

github

web-flow
Merge 9f3cfb7e4 into 6be34a4b2
Pull Request #56: Another big refactor

454 of 677 new or added lines in 9 files covered. (67.06%)

11 existing lines in 3 files now uncovered.

4786 of 5312 relevant lines covered (90.1%)

723.48 hits per line

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

93.9
/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,100✔
74
        Self {
1,100✔
75
            buffer,
1,100✔
76
            tokenize_pos: 0,
1,100✔
77
            data_end: 0,
1,100✔
78
            unescaped_len: 0,
1,100✔
79
        }
1,100✔
80
    }
1,100✔
81

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

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

102
    /// Get remaining bytes available for reading
103
    pub fn remaining_bytes(&self) -> usize {
359✔
104
        self.data_end.saturating_sub(self.tokenize_pos)
359✔
105
    }
359✔
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]> {
8,190✔
110
        if self.data_end >= self.buffer.len() {
8,190✔
111
            return None;
1,793✔
112
        }
6,397✔
113
        self.buffer.get_mut(self.data_end..)
6,397✔
114
    }
8,190✔
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,798✔
123
        if start_offset == 0 {
1,798✔
124
            // Already at start, no compaction possible
125
            return Ok(0);
530✔
126
        }
1,268✔
127

128
        let offset = start_offset;
1,268✔
129

130
        if start_offset >= self.data_end {
1,268✔
131
            // All data has been processed, reset to start
132
            self.tokenize_pos = 0;
649✔
133
            self.data_end = 0;
649✔
134
            return Ok(offset);
649✔
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 = start_offset..start_offset.wrapping_add(span_len);
619✔
150
            if src_range.end > self.buffer.len() {
619✔
151
                return Err(StreamBufferError::InvalidSliceBounds);
×
152
            }
619✔
153

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

159
        // Update positions
160
        self.tokenize_pos = self.tokenize_pos.saturating_sub(offset);
619✔
161
        self.data_end = remaining_data;
619✔
162

163
        Ok(offset)
619✔
164
    }
1,798✔
165

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

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

187
        // Ensure we have space at the start for unescaping
188
        if max_escaped_len > self.buffer.len() {
350✔
189
            return Err(StreamBufferError::BufferFull);
×
190
        }
350✔
191

192
        // Copy existing content if there is any
193
        if copy_end > copy_start && copy_start < self.data_end {
350✔
194
            let span_len = copy_end.saturating_sub(copy_start);
275✔
195

196
            // Ensure the span fits in the buffer - return error instead of silent truncation
197
            if span_len > self.buffer.len() {
275✔
198
                return Err(StreamBufferError::BufferFull);
1✔
199
            }
274✔
200

201
            let src_range = copy_start..copy_start.wrapping_add(span_len);
274✔
202
            if src_range.end > self.buffer.len() {
274✔
203
                return Err(StreamBufferError::InvalidSliceBounds);
×
204
            }
274✔
205

206
            // Copy within the same buffer: move data from [copy_start..copy_end] to [0..span_len]
207
            // Use copy_within to handle overlapping ranges safely
208
            self.buffer.copy_within(src_range, 0);
274✔
209
            self.unescaped_len = span_len;
274✔
210
        }
75✔
211

212
        Ok(())
349✔
213
    }
350✔
214

215
    /// Get the unescaped content slice
216
    pub fn get_unescaped_slice(&self) -> Result<&[u8], StreamBufferError> {
184✔
217
        if self.unescaped_len == 0 {
184✔
218
            return Err(StreamBufferError::InvalidSliceBounds);
1✔
219
        }
183✔
220
        self.buffer
183✔
221
            .get(0..self.unescaped_len)
183✔
222
            .ok_or(StreamBufferError::Unexpected)
183✔
223
    }
184✔
224

225
    /// Clear unescaped content (call after yielding unescaped string)
226
    pub fn clear_unescaped(&mut self) {
151✔
227
        self.unescaped_len = 0;
151✔
228
    }
151✔
229

230
    /// Get current tokenize position (for string start tracking)
231
    pub fn current_position(&self) -> usize {
21,308✔
232
        self.tokenize_pos
21,308✔
233
    }
21,308✔
234

235
    /// Check if buffer is empty (no more data to process)
236
    pub fn is_empty(&self) -> bool {
38,208✔
237
        self.tokenize_pos >= self.data_end
38,208✔
238
    }
38,208✔
239

240
    /// Check if we have unescaped content ready
241
    pub fn has_unescaped_content(&self) -> bool {
7,449✔
242
        self.unescaped_len > 0
7,449✔
243
    }
7,449✔
244

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

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

268
#[cfg(test)]
269
mod tests {
270
    use super::*;
271

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

278
        // Simulate some data being in the buffer
279
        let test_data = b"hello world";
1✔
280
        stream_buffer.buffer[0..test_data.len()].copy_from_slice(test_data);
1✔
281
        stream_buffer.data_end = test_data.len();
1✔
282

283
        // Test that we can get buffer data
284

285
        // Test unescaped content - add some unescaped data
286
        stream_buffer.unescaped_len = 3;
1✔
287
        stream_buffer.buffer[0..3].copy_from_slice(b"abc");
1✔
288

289
        let unescaped_slice = stream_buffer.get_unescaped_slice().unwrap();
1✔
290
        assert_eq!(unescaped_slice, b"abc");
1✔
291

292
        // The key expectation: these slices should live as long as the original buffer
293
        // and be usable to create String::Borrowed(&'buffer str) and String::Unescaped(&'buffer str)
294
    }
1✔
295

296
    #[test]
297
    fn test_new_stream_buffer() {
1✔
298
        let mut buffer = [0u8; 100];
1✔
299
        let db = StreamBuffer::new(&mut buffer);
1✔
300

301
        assert_eq!(db.tokenize_pos, 0);
1✔
302
        assert_eq!(db.data_end, 0);
1✔
303
        assert_eq!(db.unescaped_len, 0);
1✔
304
        assert!(db.is_empty());
1✔
305
    }
1✔
306

307
    #[test]
308
    fn test_fill_and_advance() {
1✔
309
        let mut buffer = [0u8; 100];
1✔
310
        let mut db = StreamBuffer::new(&mut buffer);
1✔
311

312
        // Fill with some data
313
        {
1✔
314
            let fill_slice = db.get_fill_slice().unwrap();
1✔
315
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
316
        }
1✔
317
        db.mark_filled(5).unwrap();
1✔
318

319
        assert_eq!(db.data_end, 5);
1✔
320
        assert_eq!(db.remaining_bytes(), 5);
1✔
321

322
        // Read bytes
323
        assert_eq!(db.current_byte().unwrap(), b'h');
1✔
324
        db.advance().unwrap();
1✔
325
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
326
        assert_eq!(db.remaining_bytes(), 4);
1✔
327
    }
1✔
328

329
    #[test]
330
    fn test_error_conditions() {
1✔
331
        let mut buffer = [0u8; 10];
1✔
332
        let mut db = StreamBuffer::new(&mut buffer);
1✔
333

334
        // EndOfData errors
335
        assert_eq!(db.current_byte().unwrap_err(), StreamBufferError::EndOfData);
1✔
336
        assert_eq!(db.advance().unwrap_err(), StreamBufferError::EndOfData);
1✔
337

338
        // No unescaped content
339
        assert!(db.get_unescaped_slice().is_err());
1✔
340
    }
1✔
341

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

348
        // Fill buffer completely
349
        {
1✔
350
            let fill_slice = db.get_fill_slice().unwrap();
1✔
351
            fill_slice.copy_from_slice(b"0123456789");
1✔
352
        }
1✔
353
        db.mark_filled(10).unwrap();
1✔
354

355
        // No more space for filling
356
        assert!(db.get_fill_slice().is_none());
1✔
357

358
        // We can still read from buffer
359
        assert_eq!(db.current_byte().unwrap(), b'0');
1✔
360
        assert_eq!(db.remaining_bytes(), 10);
1✔
361
    }
1✔
362

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

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

376
        // Advance through the data
377
        for _ in 0..6 {
7✔
378
            db.advance().unwrap();
6✔
379
        }
6✔
380

381
        // Now buffer is exhausted but we don't have a complete token
382
        assert!(db.is_empty());
1✔
383
        assert_eq!(db.remaining_bytes(), 0);
1✔
384

385
        // This simulates the scenario where we need more data but can't fit it
386
        // The parser would need to handle this by buffering the incomplete token
387
    }
1✔
388

389
    #[test]
390
    fn test_reader_returns_zero_bytes() {
1✔
391
        let mut buffer = [0u8; 20];
1✔
392
        let mut db = StreamBuffer::new(&mut buffer);
1✔
393

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

402
        assert!(db.is_empty());
1✔
403
        assert_eq!(db.data_end, 0);
1✔
404
        assert_eq!(db.remaining_bytes(), 0);
1✔
405

406
        // Should still be able to get fill slice for next attempt
407
        let fill_slice = db.get_fill_slice().unwrap();
1✔
408
        assert_eq!(fill_slice.len(), 20);
1✔
409
    }
1✔
410

411
    #[test]
412
    fn test_boundary_conditions() {
1✔
413
        let mut buffer = [0u8; 3]; // Absolute minimum
1✔
414
        let mut db = StreamBuffer::new(&mut buffer);
1✔
415

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

423
        // Should be able to read through it
424
        assert_eq!(db.current_byte().unwrap(), b'"');
1✔
425
        db.advance().unwrap();
1✔
426
        assert_eq!(db.current_byte().unwrap(), b'a');
1✔
427
        db.advance().unwrap();
1✔
428
        assert_eq!(db.current_byte().unwrap(), b'"');
1✔
429
        db.advance().unwrap();
1✔
430

431
        assert!(db.is_empty());
1✔
432
    }
1✔
433

434
    #[test]
435
    fn test_start_unescaping_with_copy_span_too_large() {
1✔
436
        let mut buffer = [0u8; 10]; // Small buffer
1✔
437
        let mut db = StreamBuffer::new(&mut buffer);
1✔
438

439
        // Fill buffer with some data
440
        {
1✔
441
            let fill_slice = db.get_fill_slice().unwrap();
1✔
442
            fill_slice.copy_from_slice(b"0123456789");
1✔
443
        }
1✔
444
        db.mark_filled(10).unwrap();
1✔
445

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

451
        // Should return BufferFull error instead of silently truncating
452
        let result = db.start_unescaping_with_copy(max_escaped_len, copy_start, copy_end);
1✔
453
        assert_eq!(result.unwrap_err(), StreamBufferError::BufferFull);
1✔
454

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

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

469
    #[test]
470
    fn test_append_unescaped_byte_uses_full_buffer() {
1✔
471
        let mut buffer = [0u8; 10]; // 10 byte buffer
1✔
472
        let mut db = StreamBuffer::new(&mut buffer);
1✔
473

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

480
        assert_eq!(db.unescaped_len, 10);
1✔
481

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

487
    #[test]
488
    fn test_compact_basic() {
1✔
489
        let mut buffer = [0u8; 10];
1✔
490
        let mut db = StreamBuffer::new(&mut buffer);
1✔
491

492
        // Fill buffer with data: "0123456789"
493
        {
1✔
494
            let fill_slice = db.get_fill_slice().unwrap();
1✔
495
            fill_slice.copy_from_slice(b"0123456789");
1✔
496
        }
1✔
497
        db.mark_filled(10).unwrap();
1✔
498

499
        // Process some data (advance tokenize_pos to position 4)
500
        for _ in 0..4 {
5✔
501
            db.advance().unwrap();
4✔
502
        }
4✔
503

504
        // Before compact: tokenize_pos=4, data_end=10, remaining="456789"
505
        assert_eq!(db.tokenize_pos, 4);
1✔
506
        assert_eq!(db.data_end, 10);
1✔
507
        assert_eq!(db.remaining_bytes(), 6);
1✔
508

509
        // Compact the buffer
510
        let offset = db.compact_from(4).unwrap();
1✔
511
        assert_eq!(offset, 4); // Data was moved by 4 positions
1✔
512

513
        // After compact: tokenize_pos=0, data_end=6, buffer starts with "456789"
514
        assert_eq!(db.tokenize_pos, 0);
1✔
515
        assert_eq!(db.data_end, 6);
1✔
516
        assert_eq!(db.remaining_bytes(), 6);
1✔
517

518
        // Verify the data was moved correctly
519
        assert_eq!(db.current_byte().unwrap(), b'4');
1✔
520
        db.advance().unwrap();
1✔
521
        assert_eq!(db.current_byte().unwrap(), b'5');
1✔
522
        db.advance().unwrap();
1✔
523
        assert_eq!(db.current_byte().unwrap(), b'6');
1✔
524
    }
1✔
525

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

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

542
    #[test]
543
    fn test_compact_no_op_when_at_start() {
1✔
544
        let mut buffer = [0u8; 10];
1✔
545
        let mut db = StreamBuffer::new(&mut buffer);
1✔
546

547
        // Fill buffer with data
548
        {
1✔
549
            let fill_slice = db.get_fill_slice().unwrap();
1✔
550
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
551
        }
1✔
552
        db.mark_filled(5).unwrap();
1✔
553

554
        // Don't advance tokenize_pos (stays at 0)
555
        assert_eq!(db.tokenize_pos, 0);
1✔
556
        assert_eq!(db.data_end, 5);
1✔
557

558
        // Compact should be no-op
559
        let offset = db.compact_from(0).unwrap();
1✔
560
        assert_eq!(offset, 0); // No movement occurred
1✔
561

562
        // Should be unchanged
563
        assert_eq!(db.tokenize_pos, 0);
1✔
564
        assert_eq!(db.data_end, 5);
1✔
565
        assert_eq!(db.current_byte().unwrap(), b'h');
1✔
566
    }
1✔
567

568
    #[test]
569
    fn test_compact_all_data_processed() {
1✔
570
        let mut buffer = [0u8; 10];
1✔
571
        let mut db = StreamBuffer::new(&mut buffer);
1✔
572

573
        // Fill buffer with data
574
        {
1✔
575
            let fill_slice = db.get_fill_slice().unwrap();
1✔
576
            fill_slice[0..5].copy_from_slice(b"hello");
1✔
577
        }
1✔
578
        db.mark_filled(5).unwrap();
1✔
579

580
        // Process all data
581
        for _ in 0..5 {
6✔
582
            db.advance().unwrap();
5✔
583
        }
5✔
584

585
        // All data processed
586
        assert_eq!(db.tokenize_pos, 5);
1✔
587
        assert_eq!(db.data_end, 5);
1✔
588
        assert!(db.is_empty());
1✔
589

590
        // Compact should reset to start
591
        let offset = db.compact_from(5).unwrap();
1✔
592
        assert_eq!(offset, 5); // All data was processed, moved by 5
1✔
593

594
        // Should be reset to empty state
595
        assert_eq!(db.tokenize_pos, 0);
1✔
596
        assert_eq!(db.data_end, 0);
1✔
597
        assert!(db.is_empty());
1✔
598
    }
1✔
599

600
    #[test]
601
    fn test_compact_enables_new_data_fill() {
1✔
602
        let mut buffer = [0u8; 10];
1✔
603
        let mut db = StreamBuffer::new(&mut buffer);
1✔
604

605
        // Fill buffer completely
606
        {
1✔
607
            let fill_slice = db.get_fill_slice().unwrap();
1✔
608
            fill_slice.copy_from_slice(b"0123456789");
1✔
609
        }
1✔
610
        db.mark_filled(10).unwrap();
1✔
611

612
        // Process half the data
613
        for _ in 0..5 {
6✔
614
            db.advance().unwrap();
5✔
615
        }
5✔
616

617
        // Buffer is full, can't get fill slice
618
        assert!(db.get_fill_slice().is_none());
1✔
619

620
        // Compact to make space
621
        let offset = db.compact_from(5).unwrap();
1✔
622
        assert_eq!(offset, 5); // Data moved by 5 positions
1✔
623

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

628
        // Fill with new data
629
        fill_slice[0..5].copy_from_slice(b"ABCDE");
1✔
630
        db.mark_filled(5).unwrap();
1✔
631

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

647
    #[test]
648
    fn test_compact_with_single_byte_remaining() {
1✔
649
        let mut buffer = [0u8; 5];
1✔
650
        let mut db = StreamBuffer::new(&mut buffer);
1✔
651

652
        // Fill buffer: "abcde"
653
        {
1✔
654
            let fill_slice = db.get_fill_slice().unwrap();
1✔
655
            fill_slice.copy_from_slice(b"abcde");
1✔
656
        }
1✔
657
        db.mark_filled(5).unwrap();
1✔
658

659
        // Process almost all data (leave one byte)
660
        for _ in 0..4 {
5✔
661
            db.advance().unwrap();
4✔
662
        }
4✔
663

664
        // One byte remaining
665
        assert_eq!(db.remaining_bytes(), 1);
1✔
666
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
667

668
        // Compact
669
        let offset = db.compact_from(4).unwrap();
1✔
670
        assert_eq!(offset, 4); // Moved by 4 positions
1✔
671

672
        // Should have moved the last byte to start
673
        assert_eq!(db.tokenize_pos, 0);
1✔
674
        assert_eq!(db.data_end, 1);
1✔
675
        assert_eq!(db.current_byte().unwrap(), b'e');
1✔
676
        assert_eq!(db.remaining_bytes(), 1);
1✔
677

678
        // Should have space for 4 more bytes
679
        let fill_slice = db.get_fill_slice().unwrap();
1✔
680
        assert_eq!(fill_slice.len(), 4);
1✔
681
    }
1✔
682

683
    #[test]
684
    fn test_compact_buffer_wall_scenario() {
1✔
685
        // Simulate hitting the buffer wall during token processing
686
        // This tests the "always compact when buffer full" strategy
687

688
        let mut buffer = [0u8; 10];
1✔
689
        let mut db = StreamBuffer::new(&mut buffer);
1✔
690

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

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

702
        // Advance to simulate tokenizer processing
703
        for _ in 0..10 {
11✔
704
            db.advance().unwrap();
10✔
705
        }
10✔
706

707
        // Buffer is now empty, we hit the wall
708
        assert!(db.is_empty());
1✔
709
        assert!(db.get_fill_slice().is_none()); // No space to read more
1✔
710

711
        // ALWAYS compact when hitting buffer wall
712
        let offset = db.compact_from(10).unwrap();
1✔
713
        assert_eq!(offset, 10); // Moved by 10 positions (everything was processed)
1✔
714

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

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

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

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

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

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

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

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

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

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

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

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

789
    #[test]
790
    fn test_position_update_after_compaction_normal_case() {
1✔
791
        // Test normal position updates where positions are preserved
792

793
        // Case 1: String position preserved after compaction
794
        let _state = crate::shared::State::String(10);
1✔
795
        let offset = 5;
1✔
796

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

804
        assert_eq!(updated_pos, 5);
1✔
805

806
        // Case 2: Key position preserved after compaction
807
        let key_pos = 8;
1✔
808
        let offset = 3;
1✔
809

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

816
        assert_eq!(updated_key_pos, 5);
1✔
817

818
        // Case 3: Number position preserved after compaction
819
        let number_pos = 15;
1✔
820
        let offset = 7;
1✔
821

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

829
        assert_eq!(updated_number_pos, 8);
1✔
830
    }
1✔
831

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

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

840
        let needs_escape_mode = string_pos < offset;
1✔
841
        assert!(needs_escape_mode);
1✔
842

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

849
        assert_eq!(updated_string_pos, 0);
1✔
850

851
        // Case 2: Key position discarded - needs escape mode
852
        let key_pos = 2;
1✔
853
        let offset = 8;
1✔
854

855
        let needs_escape_mode = key_pos < offset;
1✔
856
        assert!(needs_escape_mode);
1✔
857

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

864
        assert_eq!(updated_key_pos, 0);
1✔
865

866
        // Case 3: Number position discarded - should be an error
867
        let number_pos = 1;
1✔
868
        let offset = 5;
1✔
869

870
        let should_error = number_pos < offset;
1✔
871
        assert!(should_error); // Numbers spanning compaction boundaries should error
1✔
872
    }
1✔
873

874
    #[test]
875
    fn test_position_update_boundary_conditions() {
1✔
876
        // Test exact boundary conditions for position updates
877

878
        // Case 1: Position exactly equals offset
879
        let pos = 5;
1✔
880
        let offset = 5;
1✔
881

882
        let needs_escape_mode = pos < offset; // false, pos == offset
1✔
883
        assert!(!needs_escape_mode);
1✔
884

885
        let updated_pos = pos - offset; // 5 - 5 = 0
1✔
886
        assert_eq!(updated_pos, 0);
1✔
887

888
        // Case 2: Position one less than offset (boundary case)
889
        let pos = 4;
1✔
890
        let offset = 5;
1✔
891

892
        let needs_escape_mode = pos < offset; // true, pos < offset
1✔
893
        assert!(needs_escape_mode);
1✔
894

895
        // Case 3: Position one more than offset (boundary case)
896
        let pos = 6;
1✔
897
        let offset = 5;
1✔
898

899
        let needs_escape_mode = pos < offset; // false, pos > offset
1✔
900
        assert!(!needs_escape_mode);
1✔
901

902
        let updated_pos = pos - offset; // 6 - 5 = 1
1✔
903
        assert_eq!(updated_pos, 1);
1✔
904

905
        // Case 4: Zero offset (no compaction occurred)
906
        let pos = 10;
1✔
907
        let offset = 0;
1✔
908

909
        let needs_escape_mode = pos < offset; // false, 10 < 0
1✔
910
        assert!(!needs_escape_mode);
1✔
911

912
        let updated_pos = pos - offset; // 10 - 0 = 10 (unchanged)
1✔
913
        assert_eq!(updated_pos, 10);
1✔
914
    }
1✔
915

916
    #[test]
917
    fn test_position_update_state_transitions() {
1✔
918
        // Test the complete state transition logic for different parser states
919

920
        // Mock the State enum variants and position update logic
921
        use crate::shared::State;
922

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

933
        // Case 2: String state position updates
934
        let mut string_state = State::String(12);
1✔
935
        let offset = 8;
1✔
936

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

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

954
        // Case 3: Key state needing escape mode
955
        let mut key_state = State::Key(3);
1✔
956
        let offset = 10;
1✔
957

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

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

975
        // Case 4: Number state normal update
976
        let mut number_state = State::Number(20);
1✔
977
        let offset = 6;
1✔
978

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

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

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

1003
    fn current_position(&self) -> usize {
253✔
1004
        self.tokenize_pos
253✔
1005
    }
253✔
1006

1007
    fn is_empty(&self) -> bool {
×
NEW
1008
        let result = self.tokenize_pos >= self.data_end;
×
NEW
1009
        log::debug!(
×
NEW
1010
            "[NEW] StreamBuffer::is_empty(): tokenize_pos={}, data_end={}, result={}",
×
1011
            self.tokenize_pos,
1012
            self.data_end,
1013
            result
1014
        );
NEW
1015
        result
×
UNCOV
1016
    }
×
1017
}
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