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

kaidokert / picojson-rs / 16296752393

15 Jul 2025 02:54PM UTC coverage: 91.544% (-2.2%) from 93.785%
16296752393

Pull #57

github

web-flow
Merge cdc45a9eb into 6be34a4b2
Pull Request #57: Even bigger refactor

481 of 649 new or added lines in 10 files covered. (74.11%)

17 existing lines in 5 files now uncovered.

4742 of 5180 relevant lines covered (91.54%)

739.86 hits per line

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

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

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

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

102
    /// Get remaining bytes available for reading
103
    pub fn remaining_bytes(&self) -> usize {
357✔
104
        self.data_end.saturating_sub(self.tokenize_pos)
357✔
105
    }
357✔
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,224✔
110
        if self.data_end >= self.buffer.len() {
13,224✔
111
            return None;
1,792✔
112
        }
11,432✔
113
        self.buffer.get_mut(self.data_end..)
11,432✔
114
    }
13,224✔
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
        let _old_tokenize_pos = self.tokenize_pos;
619✔
165
        self.tokenize_pos = self.tokenize_pos.saturating_sub(offset);
619✔
166
        self.data_end = remaining_data;
619✔
167

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

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

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

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

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

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

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

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

217
        Ok(())
347✔
218
    }
348✔
219

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

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

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

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

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

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

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

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

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

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

288
        // Test that we can get buffer data
289

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1011
    fn is_empty(&self) -> bool {
×
1012
        self.tokenize_pos >= self.data_end
×
1013
    }
×
1014
}
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