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

kaidokert / picojson-rs / 16313593500

16 Jul 2025 07:47AM UTC coverage: 94.318% (+0.5%) from 93.864%
16313593500

Pull #62

github

web-flow
Merge 7ef4af978 into d7962e604
Pull Request #62: Big old refactor

513 of 558 new or added lines in 9 files covered. (91.94%)

3 existing lines in 2 files now uncovered.

4598 of 4875 relevant lines covered (94.32%)

648.19 hits per line

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

84.93
/picojson/src/event_processor.rs
1
// SPDX-License-Identifier: Apache-2.0
2

3
//! Shared event processing logic between SliceParser and StreamParser.
4
//!
5
//! This module extracts the common event handling patterns to reduce code duplication
6
//! while preserving the performance characteristics of each parser type.
7

8
use crate::shared::{ContentRange, Event, ParserState, State, UnexpectedState};
9
use crate::ujson::{EventToken, Tokenizer};
10
use crate::{ujson, ParseError};
11

12
/// The core parser logic that handles the unified event processing loop.
13
///
14
/// This struct contains all the shared state and logic that was previously
15
/// duplicated between SliceParser and StreamParser. It uses trait abstractions
16
/// to handle the differences in content building and byte providing.
17
pub struct ParserCore<T: ujson::BitBucket, C: ujson::DepthCounter> {
18
    /// The tokenizer that processes JSON tokens
19
    pub tokenizer: Tokenizer<T, C>,
20
    /// Parser state and event storage
21
    pub parser_state: ParserState,
22
    /// Tracks if the parser is currently inside any escape sequence (\n, \uXXXX, etc.)
23
    in_escape_sequence: bool,
24
}
25

26
impl<T: ujson::BitBucket, C: ujson::DepthCounter> ParserCore<T, C> {
27
    /// Create a new ParserCore
28
    pub fn new() -> Self {
1,131✔
29
        Self {
1,131✔
30
            tokenizer: Tokenizer::new(),
1,131✔
31
            parser_state: ParserState::new(),
1,131✔
32
            in_escape_sequence: false,
1,131✔
33
        }
1,131✔
34
    }
1,131✔
35

36
    /// Unified implementation with optional byte accumulation callback.
37
    /// This supports StreamParser-specific byte accumulation when no events are generated.
38
    /// SliceParser passes a no-op closure for byte_accumulator.
39
    pub fn next_event_impl<'a, P, F>(
5,265✔
40
        &mut self,
5,265✔
41
        provider: &'a mut P,
5,265✔
42
        escape_timing: EscapeTiming,
5,265✔
43
        mut byte_accumulator: F,
5,265✔
44
    ) -> Result<Event<'a, 'a>, ParseError>
5,265✔
45
    where
5,265✔
46
        P: ContentExtractor,
5,265✔
47
        F: FnMut(&mut P, u8) -> Result<(), ParseError>,
5,265✔
48
    {
49
        loop {
50
            while !have_events(&self.parser_state.evts) {
30,024✔
51
                if let Some(byte) = provider.next_byte()? {
21,478✔
52
                    {
53
                        clear_events(&mut self.parser_state.evts);
20,427✔
54
                        let mut callback = create_tokenizer_callback(&mut self.parser_state.evts);
20,427✔
55
                        self.tokenizer
20,427✔
56
                            .parse_chunk(&[byte], &mut callback)
20,427✔
57
                            .map_err(ParseError::TokenizerError)?;
20,427✔
58
                    }
59

60
                    // Call byte accumulator if no events were generated AND we are not in an escape sequence
61
                    if !have_events(&self.parser_state.evts) && !self.in_escape_sequence {
20,422✔
62
                        byte_accumulator(provider, byte)?;
12,134✔
63
                    }
8,288✔
64
                } else {
65
                    // Handle end of stream
66
                    {
67
                        clear_events(&mut self.parser_state.evts);
522✔
68
                        let mut callback = create_tokenizer_callback(&mut self.parser_state.evts);
522✔
69
                        self.tokenizer
522✔
70
                            .finish(&mut callback)
522✔
71
                            .map_err(ParseError::TokenizerError)?;
522✔
72
                    }
73

74
                    if !have_events(&self.parser_state.evts) {
518✔
75
                        return Ok(Event::EndDocument);
514✔
76
                    }
4✔
77
                }
78
            }
79

80
            let taken_event = take_first_event(&mut self.parser_state.evts);
8,546✔
81
            let Some(taken) = taken_event else {
8,546✔
NEW
82
                return Err(UnexpectedState::StateMismatch.into());
×
83
            };
84

85
            // Try shared event processors first
86
            if let Some(result) =
5,801✔
87
                process_simple_events(&taken).or_else(|| provider.process_begin_events(&taken))
8,546✔
88
            {
89
                match result {
5,801✔
90
                    EventResult::Complete(event) => return Ok(event),
3,154✔
91
                    EventResult::ExtractString => return provider.validate_and_extract_string(),
431✔
92
                    EventResult::ExtractKey => return provider.validate_and_extract_key(),
325✔
93
                    EventResult::ExtractNumber(from_container_end) => {
277✔
94
                        return provider.validate_and_extract_number(from_container_end)
277✔
95
                    }
96
                    EventResult::Continue => continue,
1,614✔
97
                }
98
            }
2,745✔
99

100
            // Handle parser-specific events based on escape timing
101
            match taken {
137✔
102
                ujson::Event::Begin(EventToken::EscapeSequence) => {
103
                    self.in_escape_sequence = true;
951✔
104
                    provider.process_begin_escape_sequence_event()?;
951✔
105
                }
106
                ujson::Event::Begin(EventToken::UnicodeEscape) => {
107
                    self.in_escape_sequence = true;
219✔
108
                    provider.process_unicode_escape_events(&taken)?;
219✔
109
                }
110
                ujson::Event::End(EventToken::UnicodeEscape) => {
111
                    self.in_escape_sequence = false;
201✔
112
                    provider.process_unicode_escape_events(&taken)?;
201✔
113
                }
114
                ujson::Event::Begin(
115
                    escape_token @ (EventToken::EscapeQuote
1✔
116
                    | EventToken::EscapeBackslash
117
                    | EventToken::EscapeSlash
118
                    | EventToken::EscapeBackspace
119
                    | EventToken::EscapeFormFeed
120
                    | EventToken::EscapeNewline
121
                    | EventToken::EscapeCarriageReturn
122
                    | EventToken::EscapeTab),
123
                ) if escape_timing == EscapeTiming::OnBegin => {
137✔
124
                    // For SliceParser, the escape is handled in a single event.
125
                    // It begins and ends within this block.
126
                    self.in_escape_sequence = true;
20✔
127
                    provider.process_simple_escape_event(&escape_token)?;
20✔
128
                    self.in_escape_sequence = false;
18✔
129
                }
130
                ujson::Event::End(
131
                    escape_token @ (EventToken::EscapeQuote
136✔
132
                    | EventToken::EscapeBackslash
133
                    | EventToken::EscapeSlash
134
                    | EventToken::EscapeBackspace
135
                    | EventToken::EscapeFormFeed
136
                    | EventToken::EscapeNewline
137
                    | EventToken::EscapeCarriageReturn
138
                    | EventToken::EscapeTab),
139
                ) if escape_timing == EscapeTiming::OnEnd => {
137✔
140
                    // For StreamParser, the escape ends here.
141
                    provider.process_simple_escape_event(&escape_token)?;
668✔
142
                    self.in_escape_sequence = false;
668✔
143
                }
144
                _ => {
686✔
145
                    // All other events continue to next iteration
686✔
146
                }
686✔
147
            }
148
        }
149
    }
5,265✔
150
}
151

152
impl<T: ujson::BitBucket, C: ujson::DepthCounter> Default for ParserCore<T, C> {
NEW
153
    fn default() -> Self {
×
NEW
154
        Self::new()
×
UNCOV
155
    }
×
156
}
157

158
/// Enum to specify when escape sequences should be processed
159
#[derive(Debug, Clone, Copy, PartialEq)]
160
pub enum EscapeTiming {
161
    /// Process simple escape sequences on Begin events (SliceParser)
162
    OnBegin,
163
    /// Process simple escape sequences on End events (StreamParser)
164
    OnEnd,
165
}
166

167
/// Result of processing a tokenizer event
168
#[derive(Debug)]
169
pub enum EventResult<'a, 'b> {
170
    /// Event processing is complete, return this event to the user
171
    Complete(Event<'a, 'b>),
172
    /// Continue processing more tokenizer events
173
    Continue,
174
    /// Extract string content (delegate to parser-specific logic)
175
    ExtractString,
176
    /// Extract key content (delegate to parser-specific logic)
177
    ExtractKey,
178
    /// Extract number content (delegate to parser-specific logic)
179
    /// bool indicates if number was terminated by container delimiter
180
    ExtractNumber(bool),
181
}
182

183
/// Trait for content extraction operations that differ between parsers
184
/// Consolidates ParserContext and ContentExtractor functionality
185
pub trait ContentExtractor {
186
    /// Get the next byte from the input source
187
    /// Returns None when end of input is reached
188
    fn next_byte(&mut self) -> Result<Option<u8>, ParseError>;
189

190
    /// Get current position in the input
191
    fn current_position(&self) -> usize;
192

193
    /// Begin string/key content processing at current position
194
    fn begin_string_content(&mut self, pos: usize);
195

196
    /// Get mutable access to parser state
197
    fn parser_state_mut(&mut self) -> &mut crate::shared::State;
198

199
    /// Get mutable access to the Unicode escape collector
200
    /// This eliminates the need for wrapper methods that just forward calls
201
    fn unicode_escape_collector_mut(
202
        &mut self,
203
    ) -> &mut crate::escape_processor::UnicodeEscapeCollector;
204

205
    /// Extract string content using parser-specific logic
206
    fn extract_string_content(
207
        &mut self,
208
        start_pos: usize,
209
    ) -> Result<crate::Event<'_, '_>, crate::ParseError>;
210

211
    /// Extract key content using parser-specific logic
212
    fn extract_key_content(
213
        &mut self,
214
        start_pos: usize,
215
    ) -> Result<crate::Event<'_, '_>, crate::ParseError>;
216

217
    /// Extract a completed number using shared number parsing logic
218
    ///
219
    /// # Arguments
220
    /// * `start_pos` - Position where the number started
221
    /// * `from_container_end` - True if number was terminated by container delimiter
222
    /// * `finished` - True if the parser has finished processing input (StreamParser-specific)
223
    fn extract_number(
224
        &mut self,
225
        start_pos: usize,
226
        from_container_end: bool,
227
        finished: bool,
228
    ) -> Result<crate::Event<'_, '_>, crate::ParseError>;
229

230
    /// Check if the underlying source is finished (e.g., EOF for a stream).
231
    /// The default is `true`, suitable for complete sources like slices.
NEW
232
    fn is_finished(&self) -> bool {
×
NEW
233
        true
×
NEW
234
    }
×
235

236
    /// Shared validation and extraction for string content
237
    fn validate_and_extract_string(&mut self) -> Result<crate::Event<'_, '_>, crate::ParseError> {
431✔
238
        let start_pos = match *self.parser_state() {
431✔
239
            crate::shared::State::String(pos) => pos,
431✔
240
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
241
        };
242

243
        // Check for incomplete surrogate pairs before ending the string
244
        if self
431✔
245
            .unicode_escape_collector_mut()
431✔
246
            .has_pending_high_surrogate()
431✔
247
        {
248
            return Err(crate::ParseError::InvalidUnicodeCodepoint);
3✔
249
        }
428✔
250

251
        *self.parser_state_mut() = crate::shared::State::None;
428✔
252
        self.extract_string_content(start_pos)
428✔
253
    }
431✔
254

255
    /// Shared validation and extraction for key content
256
    fn validate_and_extract_key(&mut self) -> Result<crate::Event<'_, '_>, crate::ParseError> {
325✔
257
        let start_pos = match *self.parser_state() {
325✔
258
            crate::shared::State::Key(pos) => pos,
325✔
259
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
260
        };
261

262
        // Check for incomplete surrogate pairs before ending the key
263
        if self
325✔
264
            .unicode_escape_collector_mut()
325✔
265
            .has_pending_high_surrogate()
325✔
266
        {
267
            return Err(crate::ParseError::InvalidUnicodeCodepoint);
×
268
        }
325✔
269

270
        *self.parser_state_mut() = crate::shared::State::None;
325✔
271
        self.extract_key_content(start_pos)
325✔
272
    }
325✔
273

274
    /// Shared validation and extraction for number content
275
    fn validate_and_extract_number(
27✔
276
        &mut self,
27✔
277
        from_container_end: bool,
27✔
278
    ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
27✔
279
        let start_pos = match *self.parser_state() {
27✔
280
            crate::shared::State::Number(pos) => pos,
27✔
281
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
282
        };
283

284
        *self.parser_state_mut() = crate::shared::State::None;
27✔
285
        self.extract_number(start_pos, from_container_end, true)
27✔
286
    }
27✔
287

288
    /// Get the current parser state for escape context checking
289
    fn parser_state(&self) -> &crate::shared::State;
290

291
    /// Process Unicode escape sequence using shared collector logic
292
    fn process_unicode_escape_with_collector(&mut self) -> Result<(), crate::ParseError>;
293

294
    /// Handle a simple escape character (after EscapeProcessor conversion)
295
    fn handle_simple_escape_char(&mut self, escape_char: u8) -> Result<(), crate::ParseError>;
296

297
    /// Begin escape sequence processing (lifecycle method with default no-op implementation)
298
    /// Called when escape sequence processing begins (e.g., on Begin(EscapeSequence))
299
    fn begin_escape_sequence(&mut self) -> Result<(), crate::ParseError>;
300

301
    /// Begin unicode escape sequence processing
302
    fn begin_unicode_escape(&mut self) -> Result<(), crate::ParseError>;
303

304
    /// Process Begin events that have similar patterns between parsers
305
    fn process_begin_events(
4,366✔
306
        &mut self,
4,366✔
307
        event: &crate::ujson::Event,
4,366✔
308
    ) -> Option<EventResult<'static, 'static>> {
4,366✔
309
        match event {
3,479✔
310
            // String/Key Begin events - nearly identical patterns
311
            crate::ujson::Event::Begin(EventToken::Key) => {
312
                let pos = self.current_position();
356✔
313
                *self.parser_state_mut() = State::Key(pos);
356✔
314
                self.begin_string_content(pos);
356✔
315
                Some(EventResult::Continue)
356✔
316
            }
317
            crate::ujson::Event::Begin(EventToken::String) => {
318
                let pos = self.current_position();
768✔
319
                *self.parser_state_mut() = State::String(pos);
768✔
320
                self.begin_string_content(pos);
768✔
321
                Some(EventResult::Continue)
768✔
322
            }
323

324
            // Number Begin events - identical logic
325
            crate::ujson::Event::Begin(
326
                EventToken::Number | EventToken::NumberAndArray | EventToken::NumberAndObject,
327
            ) => {
328
                let pos = self.current_position();
471✔
329
                let number_start = ContentRange::number_start_from_current(pos);
471✔
330
                *self.parser_state_mut() = State::Number(number_start);
471✔
331
                Some(EventResult::Continue)
471✔
332
            }
333

334
            // Primitive Begin events - identical logic
335
            crate::ujson::Event::Begin(EventToken::True | EventToken::False | EventToken::Null) => {
336
                Some(EventResult::Continue)
25✔
337
            }
338

339
            _ => None,
2,746✔
340
        }
341
    }
4,366✔
342

343
    /// Process Begin(EscapeSequence) events using the enhanced lifecycle interface
344
    fn process_begin_escape_sequence_event(&mut self) -> Result<(), crate::ParseError> {
951✔
345
        // Only process if we're inside a string or key
346
        match self.parser_state() {
951✔
347
            crate::shared::State::String(_) | crate::shared::State::Key(_) => {
348
                self.begin_escape_sequence()?;
951✔
349
            }
NEW
350
            _ => {} // Ignore if not in string/key context
×
351
        }
352
        Ok(())
951✔
353
    }
951✔
354

355
    /// Process simple escape sequence events that have similar patterns between parsers
356
    fn process_simple_escape_event(
688✔
357
        &mut self,
688✔
358
        escape_token: &EventToken,
688✔
359
    ) -> Result<(), crate::ParseError> {
688✔
360
        // Clear any pending high surrogate state when we encounter a simple escape
361
        // This ensures that interrupted surrogate pairs (like \uD801\n\uDC37) are properly rejected
362
        self.unicode_escape_collector_mut().reset_all();
688✔
363

364
        // Use unified escape token processing from EscapeProcessor
365
        let unescaped_char =
688✔
366
            crate::escape_processor::EscapeProcessor::process_escape_token(escape_token)?;
688✔
367

368
        // Only process if we're inside a string or key
369
        match self.parser_state() {
688✔
370
            crate::shared::State::String(_) | crate::shared::State::Key(_) => {
371
                self.handle_simple_escape_char(unescaped_char)?;
688✔
372
            }
NEW
373
            _ => {} // Ignore if not in string/key context
×
374
        }
375

376
        Ok(())
686✔
377
    }
688✔
378

379
    /// Process Unicode escape begin/end events that have similar patterns between parsers
380
    fn process_unicode_escape_events(
420✔
381
        &mut self,
420✔
382
        event: &crate::ujson::Event,
420✔
383
    ) -> Result<bool, crate::ParseError> {
420✔
384
        match event {
219✔
385
            crate::ujson::Event::Begin(EventToken::UnicodeEscape) => {
386
                // Start Unicode escape collection - reset collector for new sequence
387
                // Only handle if we're inside a string or key
388
                match self.parser_state() {
219✔
389
                    crate::shared::State::String(_) | crate::shared::State::Key(_) => {
390
                        self.unicode_escape_collector_mut().reset();
219✔
391
                        self.begin_unicode_escape()?;
219✔
392
                    }
NEW
393
                    _ => {} // Ignore if not in string/key context
×
394
                }
395
                Ok(true) // Event was handled
219✔
396
            }
397
            crate::ujson::Event::End(EventToken::UnicodeEscape) => {
398
                // Handle end of Unicode escape sequence (\uXXXX)
399
                match self.parser_state() {
201✔
400
                    crate::shared::State::String(_) | crate::shared::State::Key(_) => {
401
                        self.process_unicode_escape_with_collector()?;
201✔
402
                    }
NEW
403
                    _ => {} // Ignore if not in string/key context
×
404
                }
405
                Ok(true) // Event was handled
177✔
406
            }
NEW
407
            _ => Ok(false), // Event was not handled
×
408
        }
409
    }
420✔
410
}
411

412
/// Clear event storage array - utility function
413
pub fn clear_events(event_storage: &mut [Option<crate::ujson::Event>; 2]) {
20,949✔
414
    event_storage[0] = None;
20,949✔
415
    event_storage[1] = None;
20,949✔
416
}
20,949✔
417

418
/// Creates a standard tokenizer callback for event storage
419
///
420
/// This callback stores tokenizer events in the parser's event array, filling the first
421
/// available slot. This pattern is identical across both SliceParser and StreamParser.
422
pub fn create_tokenizer_callback(
20,952✔
423
    event_storage: &mut [Option<crate::ujson::Event>; 2],
20,952✔
424
) -> impl FnMut(crate::ujson::Event, usize) + '_ {
20,952✔
425
    |event, _len| {
8,559✔
426
        for evt in event_storage.iter_mut() {
9,471✔
427
            if evt.is_none() {
9,471✔
428
                *evt = Some(event);
8,558✔
429
                return;
8,558✔
430
            }
913✔
431
        }
432
    }
8,559✔
433
}
20,952✔
434

435
/// Shared utility to check if any events are waiting to be processed
436
pub fn have_events(event_storage: &[Option<crate::ujson::Event>; 2]) -> bool {
50,967✔
437
    event_storage.iter().any(|evt| evt.is_some())
86,641✔
438
}
50,967✔
439

440
/// Shared utility to extract the first available event from storage
441
pub fn take_first_event(
8,549✔
442
    event_storage: &mut [Option<crate::ujson::Event>; 2],
8,549✔
443
) -> Option<crate::ujson::Event> {
8,549✔
444
    event_storage.iter_mut().find_map(|e| e.take())
9,451✔
445
}
8,549✔
446

447
/// Process simple container and primitive events that are identical between parsers
448
pub fn process_simple_events(event: &crate::ujson::Event) -> Option<EventResult<'static, 'static>> {
8,555✔
449
    match event {
1,947✔
450
        // Container events - identical processing
451
        crate::ujson::Event::ObjectStart => Some(EventResult::Complete(Event::StartObject)),
251✔
452
        crate::ujson::Event::ObjectEnd => Some(EventResult::Complete(Event::EndObject)),
206✔
453
        crate::ujson::Event::ArrayStart => Some(EventResult::Complete(Event::StartArray)),
1,670✔
454
        crate::ujson::Event::ArrayEnd => Some(EventResult::Complete(Event::EndArray)),
1,007✔
455

456
        // Primitive values - identical processing
457
        crate::ujson::Event::End(EventToken::True) => {
458
            Some(EventResult::Complete(Event::Bool(true)))
11✔
459
        }
460
        crate::ujson::Event::End(EventToken::False) => {
461
            Some(EventResult::Complete(Event::Bool(false)))
6✔
462
        }
463
        crate::ujson::Event::End(EventToken::Null) => Some(EventResult::Complete(Event::Null)),
7✔
464

465
        // Content extraction triggers - identical logic
466
        crate::ujson::Event::End(EventToken::String) => Some(EventResult::ExtractString),
432✔
467
        crate::ujson::Event::End(EventToken::Key) => Some(EventResult::ExtractKey),
325✔
468
        crate::ujson::Event::End(EventToken::Number) => Some(EventResult::ExtractNumber(false)),
56✔
469
        crate::ujson::Event::End(EventToken::NumberAndArray) => {
470
            Some(EventResult::ExtractNumber(true))
140✔
471
        }
472
        crate::ujson::Event::End(EventToken::NumberAndObject) => {
473
            Some(EventResult::ExtractNumber(true))
83✔
474
        }
475

476
        // All other events need parser-specific handling
477
        _ => None,
4,361✔
478
    }
479
}
8,555✔
480

481
#[cfg(test)]
482
mod tests {
483
    use super::*;
484

485
    #[test]
486
    fn test_container_events() {
1✔
487
        assert!(matches!(
1✔
488
            process_simple_events(&crate::ujson::Event::ObjectStart),
1✔
489
            Some(EventResult::Complete(Event::StartObject))
490
        ));
491

492
        assert!(matches!(
1✔
493
            process_simple_events(&crate::ujson::Event::ArrayEnd),
1✔
494
            Some(EventResult::Complete(Event::EndArray))
495
        ));
496
    }
1✔
497

498
    #[test]
499
    fn test_primitive_events() {
1✔
500
        assert!(matches!(
1✔
501
            process_simple_events(&crate::ujson::Event::End(EventToken::True)),
1✔
502
            Some(EventResult::Complete(Event::Bool(true)))
503
        ));
504

505
        assert!(matches!(
1✔
506
            process_simple_events(&crate::ujson::Event::End(EventToken::Null)),
1✔
507
            Some(EventResult::Complete(Event::Null))
508
        ));
509
    }
1✔
510

511
    #[test]
512
    fn test_extraction_triggers() {
1✔
513
        assert!(matches!(
1✔
514
            process_simple_events(&crate::ujson::Event::End(EventToken::String)),
1✔
515
            Some(EventResult::ExtractString)
516
        ));
517

518
        assert!(matches!(
1✔
519
            process_simple_events(&crate::ujson::Event::End(EventToken::Number)),
1✔
520
            Some(EventResult::ExtractNumber(false))
521
        ));
522

523
        assert!(matches!(
1✔
524
            process_simple_events(&crate::ujson::Event::End(EventToken::NumberAndArray)),
1✔
525
            Some(EventResult::ExtractNumber(true))
526
        ));
527
    }
1✔
528

529
    #[test]
530
    fn test_complex_events_not_handled() {
1✔
531
        assert!(process_simple_events(&crate::ujson::Event::Begin(EventToken::String)).is_none());
1✔
532
        assert!(
1✔
533
            process_simple_events(&crate::ujson::Event::Begin(EventToken::EscapeQuote)).is_none()
1✔
534
        );
535
    }
1✔
536

537
    // Mock ContentExtractor for testing
538
    struct MockContentExtractor {
539
        position: usize,
540
        state: crate::shared::State,
541
        string_begin_calls: Vec<usize>,
542
    }
543

544
    impl MockContentExtractor {
545
        fn new() -> Self {
5✔
546
            Self {
5✔
547
                position: 42,
5✔
548
                state: crate::shared::State::None,
5✔
549
                string_begin_calls: Vec::new(),
5✔
550
            }
5✔
551
        }
5✔
552
    }
553

554
    impl ContentExtractor for MockContentExtractor {
NEW
555
        fn next_byte(&mut self) -> Result<Option<u8>, crate::ParseError> {
×
NEW
556
            Ok(None)
×
UNCOV
557
        }
×
558

559
        fn current_position(&self) -> usize {
3✔
560
            self.position
3✔
561
        }
3✔
562

563
        fn begin_string_content(&mut self, pos: usize) {
2✔
564
            self.string_begin_calls.push(pos);
2✔
565
        }
2✔
566

567
        fn parser_state_mut(&mut self) -> &mut crate::shared::State {
3✔
568
            &mut self.state
3✔
569
        }
3✔
570

571
        fn unicode_escape_collector_mut(
×
572
            &mut self,
×
573
        ) -> &mut crate::escape_processor::UnicodeEscapeCollector {
×
574
            unimplemented!("Mock doesn't need unicode collector")
×
575
        }
576

577
        fn extract_string_content(
×
578
            &mut self,
×
579
            _start_pos: usize,
×
580
        ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
×
581
            unimplemented!("Mock doesn't need extraction")
×
582
        }
583

584
        fn extract_key_content(
×
585
            &mut self,
×
586
            _start_pos: usize,
×
587
        ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
×
588
            unimplemented!("Mock doesn't need extraction")
×
589
        }
590

NEW
591
        fn extract_number(
×
592
            &mut self,
×
593
            _start_pos: usize,
×
594
            _from_container_end: bool,
×
NEW
595
            _finished: bool,
×
596
        ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
×
597
            unimplemented!("Mock doesn't need extraction")
×
598
        }
599

NEW
600
        fn parser_state(&self) -> &crate::shared::State {
×
NEW
601
            &self.state
×
NEW
602
        }
×
603

NEW
604
        fn process_unicode_escape_with_collector(&mut self) -> Result<(), crate::ParseError> {
×
NEW
605
            Ok(())
×
NEW
606
        }
×
607

NEW
608
        fn handle_simple_escape_char(&mut self, _escape_char: u8) -> Result<(), crate::ParseError> {
×
NEW
609
            Ok(())
×
NEW
610
        }
×
611

NEW
612
        fn begin_unicode_escape(&mut self) -> Result<(), crate::ParseError> {
×
NEW
613
            Ok(())
×
NEW
614
        }
×
615

NEW
616
        fn begin_escape_sequence(&mut self) -> Result<(), crate::ParseError> {
×
NEW
617
            Ok(())
×
NEW
618
        }
×
619
    }
620

621
    #[test]
622
    fn test_begin_events_key() {
1✔
623
        let mut context = MockContentExtractor::new();
1✔
624
        let event = crate::ujson::Event::Begin(EventToken::Key);
1✔
625

626
        let result = context.process_begin_events(&event);
1✔
627

628
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
629
        assert!(matches!(context.state, crate::shared::State::Key(42)));
1✔
630
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
631
    }
1✔
632

633
    #[test]
634
    fn test_begin_events_string() {
1✔
635
        let mut context = MockContentExtractor::new();
1✔
636
        let event = crate::ujson::Event::Begin(EventToken::String);
1✔
637

638
        let result = context.process_begin_events(&event);
1✔
639

640
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
641
        assert!(matches!(context.state, crate::shared::State::String(42)));
1✔
642
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
643
    }
1✔
644

645
    #[test]
646
    fn test_begin_events_number() {
1✔
647
        let mut context = MockContentExtractor::new();
1✔
648
        let event = crate::ujson::Event::Begin(EventToken::Number);
1✔
649

650
        let result = context.process_begin_events(&event);
1✔
651

652
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
653
        // Number should get position adjusted by ContentRange::number_start_from_current
654
        assert!(matches!(context.state, crate::shared::State::Number(_)));
1✔
655
        assert_eq!(context.string_begin_calls, Vec::<usize>::new()); // No string calls for numbers
1✔
656
    }
1✔
657

658
    #[test]
659
    fn test_begin_events_primitives() {
1✔
660
        let mut context = MockContentExtractor::new();
1✔
661

662
        for token in [EventToken::True, EventToken::False, EventToken::Null] {
3✔
663
            let event = crate::ujson::Event::Begin(token);
3✔
664
            let result = context.process_begin_events(&event);
3✔
665
            assert!(matches!(result, Some(EventResult::Continue)));
3✔
666
        }
667

668
        // Should not affect state or string processing
669
        assert!(matches!(context.state, crate::shared::State::None));
1✔
670
        assert!(context.string_begin_calls.is_empty());
1✔
671
    }
1✔
672

673
    #[test]
674
    fn test_begin_events_not_handled() {
1✔
675
        let mut context = MockContentExtractor::new();
1✔
676
        let event = crate::ujson::Event::Begin(EventToken::EscapeQuote);
1✔
677

678
        let result = context.process_begin_events(&event);
1✔
679

680
        assert!(result.is_none());
1✔
681
        assert!(matches!(context.state, crate::shared::State::None));
1✔
682
        assert!(context.string_begin_calls.is_empty());
1✔
683
    }
1✔
684

685
    #[test]
686
    fn test_tokenizer_callback() {
1✔
687
        let mut event_storage = [None, None];
1✔
688

689
        // Initially no events
690
        assert!(!have_events(&event_storage));
1✔
691

692
        {
1✔
693
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
694

1✔
695
            // Add first event
1✔
696
            callback(crate::ujson::Event::ObjectStart, 1);
1✔
697
        }
1✔
698
        assert!(have_events(&event_storage));
1✔
699
        assert!(event_storage[0].is_some());
1✔
700
        assert!(event_storage[1].is_none());
1✔
701

702
        {
1✔
703
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
704
            // Add second event
1✔
705
            callback(crate::ujson::Event::ArrayStart, 1);
1✔
706
        }
1✔
707
        assert!(event_storage[0].is_some());
1✔
708
        assert!(event_storage[1].is_some());
1✔
709

710
        {
1✔
711
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
712
            // Storage is full, third event should be ignored (no panic)
1✔
713
            callback(crate::ujson::Event::ObjectEnd, 1);
1✔
714
        }
1✔
715
        assert!(event_storage[0].is_some());
1✔
716
        assert!(event_storage[1].is_some());
1✔
717
    }
1✔
718

719
    #[test]
720
    fn test_event_extraction() {
1✔
721
        let mut event_storage = [
1✔
722
            Some(crate::ujson::Event::ObjectStart),
1✔
723
            Some(crate::ujson::Event::ArrayStart),
1✔
724
        ];
1✔
725

726
        // Extract first event
727
        let first = take_first_event(&mut event_storage);
1✔
728
        assert!(matches!(first, Some(crate::ujson::Event::ObjectStart)));
1✔
729
        assert!(event_storage[0].is_none());
1✔
730
        assert!(event_storage[1].is_some());
1✔
731

732
        // Extract second event
733
        let second = take_first_event(&mut event_storage);
1✔
734
        assert!(matches!(second, Some(crate::ujson::Event::ArrayStart)));
1✔
735
        assert!(event_storage[0].is_none());
1✔
736
        assert!(event_storage[1].is_none());
1✔
737

738
        // No more events
739
        let none = take_first_event(&mut event_storage);
1✔
740
        assert!(none.is_none());
1✔
741
        assert!(!have_events(&event_storage));
1✔
742
    }
1✔
743
}
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