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

kaidokert / picojson-rs / 16312684543

16 Jul 2025 07:00AM UTC coverage: 94.209% (+0.3%) from 93.864%
16312684543

Pull #60

github

web-flow
Merge 11dcc1589 into d7962e604
Pull Request #60: Clean refactor

518 of 569 new or added lines in 9 files covered. (91.04%)

3 existing lines in 2 files now uncovered.

4604 of 4887 relevant lines covered (94.21%)

646.6 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
// --- Content from former parser_core.rs ---
13

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

341
            _ => None,
2,746✔
342
        }
343
    }
4,366✔
344

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

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

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

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

378
        Ok(())
686✔
379
    }
688✔
380

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

628
        let result = context.process_begin_events(&event);
1✔
629

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

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

640
        let result = context.process_begin_events(&event);
1✔
641

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

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

652
        let result = context.process_begin_events(&event);
1✔
653

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

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

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

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

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

680
        let result = context.process_begin_events(&event);
1✔
681

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

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

691
        // Initially no events
692
        assert!(!have_events(&event_storage));
1✔
693

694
        {
1✔
695
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
696

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

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

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

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

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

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

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