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

kaidokert / picojson-rs / 16710654228

03 Aug 2025 11:34PM UTC coverage: 94.472% (-0.04%) from 94.51%
16710654228

push

github

web-flow
Introduce DataSource abstraction (#80)

156 of 163 new or added lines in 7 files covered. (95.71%)

70 existing lines in 7 files now uncovered.

4648 of 4920 relevant lines covered (94.47%)

664.44 hits per line

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

86.19
/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::escape_processor::{EscapeProcessor, UnicodeEscapeCollector};
9
use crate::shared::{ContentRange, Event, ParserState, State, UnexpectedState};
10
use crate::ujson::{EventToken, Tokenizer};
11
use crate::{ujson, ParseError};
12

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

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

37
    /// Unified implementation with optional byte accumulation callback.
38
    /// This supports StreamParser-specific byte accumulation when no events are generated.
39
    /// SliceParser passes a no-op closure for byte_accumulator.
40
    pub fn next_event_impl<'a, P, F>(
5,265✔
41
        &mut self,
5,265✔
42
        provider: &'a mut P,
5,265✔
43
        escape_timing: EscapeTiming,
5,265✔
44
        byte_accumulator: F,
5,265✔
45
    ) -> Result<Event<'a, 'a>, ParseError>
5,265✔
46
    where
5,265✔
47
        P: ContentExtractor,
5,265✔
48
        F: FnMut(&mut P, u8) -> Result<(), ParseError>,
5,265✔
49
    {
50
        self.next_event_impl_with_flags(provider, escape_timing, byte_accumulator, false)
5,265✔
51
    }
5,265✔
52

53
    /// Extended version with flags for specialized behavior
54
    pub fn next_event_impl_with_flags<'a, P, F>(
5,265✔
55
        &mut self,
5,265✔
56
        provider: &'a mut P,
5,265✔
57
        escape_timing: EscapeTiming,
5,265✔
58
        mut byte_accumulator: F,
5,265✔
59
        always_accumulate_during_escapes: bool,
5,265✔
60
    ) -> Result<Event<'a, 'a>, ParseError>
5,265✔
61
    where
5,265✔
62
        P: ContentExtractor,
5,265✔
63
        F: FnMut(&mut P, u8) -> Result<(), ParseError>,
5,265✔
64
    {
65
        loop {
66
            while !have_events(&self.parser_state.evts) {
30,024✔
67
                if let Some(byte) = provider.next_byte()? {
21,478✔
68
                    {
69
                        clear_events(&mut self.parser_state.evts);
20,427✔
70
                        let mut callback = create_tokenizer_callback(&mut self.parser_state.evts);
20,427✔
71
                        self.tokenizer
20,427✔
72
                            .parse_chunk(&[byte], &mut callback)
20,427✔
73
                            .map_err(ParseError::TokenizerError)?;
20,427✔
74
                    }
75

76
                    let should_accumulate = if always_accumulate_during_escapes {
20,422✔
NEW
UNCOV
77
                        if self.in_escape_sequence {
×
NEW
UNCOV
78
                            true // Always accumulate during escape sequences
×
79
                        } else {
NEW
UNCOV
80
                            !have_events(&self.parser_state.evts) // Normal behavior outside escapes
×
81
                        }
82
                    } else {
83
                        !have_events(&self.parser_state.evts) && !self.in_escape_sequence
20,422✔
84
                    };
85

86
                    if should_accumulate {
20,422✔
87
                        byte_accumulator(provider, byte)?;
12,134✔
88
                    }
8,288✔
89
                } else {
90
                    {
91
                        let mut finish_callback =
522✔
92
                            create_tokenizer_callback(&mut self.parser_state.evts);
522✔
93
                        let _bytes_processed = self.tokenizer.finish(&mut finish_callback)?;
522✔
94
                    } // Drop the callback to release the borrow
95

96
                    // If finish() generated events, process them. Otherwise, return EndDocument.
97
                    if !have_events(&self.parser_state.evts) {
518✔
98
                        return Ok(Event::EndDocument);
514✔
99
                    }
4✔
100
                }
101
            }
102

103
            let taken_event = take_first_event(&mut self.parser_state.evts);
8,546✔
104
            let Some(taken) = taken_event else {
8,546✔
UNCOV
105
                return Err(UnexpectedState::StateMismatch.into());
×
106
            };
107

108
            // Try shared event processors first
109
            if let Some(result) =
5,801✔
110
                process_simple_events(&taken).or_else(|| provider.process_begin_events(&taken))
8,546✔
111
            {
112
                match result {
5,801✔
113
                    EventResult::Complete(event) => return Ok(event),
3,154✔
114
                    EventResult::ExtractString => return provider.validate_and_extract_string(),
431✔
115
                    EventResult::ExtractKey => return provider.validate_and_extract_key(),
325✔
116
                    EventResult::ExtractNumber(from_container_end) => {
277✔
117
                        return provider.validate_and_extract_number(from_container_end)
277✔
118
                    }
119
                    EventResult::Continue => continue,
1,614✔
120
                }
121
            }
2,745✔
122

123
            // Handle parser-specific events based on escape timing
124
            match taken {
137✔
125
                ujson::Event::Begin(EventToken::EscapeSequence) => {
126
                    self.in_escape_sequence = true;
951✔
127
                    provider.process_begin_escape_sequence_event()?;
951✔
128
                }
129
                ujson::Event::Begin(EventToken::UnicodeEscape) => {
130
                    self.in_escape_sequence = true;
219✔
131
                    provider.process_unicode_escape_events(&taken)?;
219✔
132
                }
133
                ujson::Event::End(EventToken::UnicodeEscape) => {
134
                    self.in_escape_sequence = false;
201✔
135
                    provider.process_unicode_escape_events(&taken)?;
201✔
136
                }
137
                ujson::Event::Begin(
138
                    escape_token @ (EventToken::EscapeQuote
1✔
139
                    | EventToken::EscapeBackslash
140
                    | EventToken::EscapeSlash
141
                    | EventToken::EscapeBackspace
142
                    | EventToken::EscapeFormFeed
143
                    | EventToken::EscapeNewline
144
                    | EventToken::EscapeCarriageReturn
145
                    | EventToken::EscapeTab),
146
                ) if escape_timing == EscapeTiming::OnBegin => {
137✔
147
                    // For SliceParser, the escape is handled in a single event.
148
                    // It begins and ends within this block.
149
                    self.in_escape_sequence = true;
20✔
150
                    provider.process_simple_escape_event(&escape_token)?;
20✔
151
                    self.in_escape_sequence = false;
18✔
152
                }
153
                ujson::Event::End(
154
                    escape_token @ (EventToken::EscapeQuote
136✔
155
                    | EventToken::EscapeBackslash
156
                    | EventToken::EscapeSlash
157
                    | EventToken::EscapeBackspace
158
                    | EventToken::EscapeFormFeed
159
                    | EventToken::EscapeNewline
160
                    | EventToken::EscapeCarriageReturn
161
                    | EventToken::EscapeTab),
162
                ) if escape_timing == EscapeTiming::OnEnd => {
137✔
163
                    // For StreamParser, the escape ends here.
164
                    provider.process_simple_escape_event(&escape_token)?;
668✔
165
                    self.in_escape_sequence = false;
668✔
166
                }
167
                _ => {
686✔
168
                    // All other events continue to next iteration
686✔
169
                }
686✔
170
            }
171
        }
172
    }
5,265✔
173
}
174

175
impl<T: ujson::BitBucket, C: ujson::DepthCounter> Default for ParserCore<T, C> {
UNCOV
176
    fn default() -> Self {
×
UNCOV
177
        Self::new()
×
UNCOV
178
    }
×
179
}
180

181
/// Enum to specify when escape sequences should be processed
182
#[derive(Debug, Clone, Copy, PartialEq)]
183
pub enum EscapeTiming {
184
    /// Process simple escape sequences on Begin events (SliceParser)
185
    OnBegin,
186
    /// Process simple escape sequences on End events (StreamParser)
187
    OnEnd,
188
}
189

190
/// Result of processing a tokenizer event
191
#[derive(Debug)]
192
pub enum EventResult<'a, 'b> {
193
    /// Event processing is complete, return this event to the user
194
    Complete(Event<'a, 'b>),
195
    /// Continue processing more tokenizer events
196
    Continue,
197
    /// Extract string content (delegate to parser-specific logic)
198
    ExtractString,
199
    /// Extract key content (delegate to parser-specific logic)
200
    ExtractKey,
201
    /// Extract number content (delegate to parser-specific logic)
202
    /// bool indicates if number was terminated by container delimiter
203
    ExtractNumber(bool),
204
}
205

206
/// Trait for content extraction operations that differ between parsers
207
/// Consolidates ParserContext and ContentExtractor functionality
208
pub trait ContentExtractor {
209
    /// Get the next byte from the input source
210
    /// Returns None when end of input is reached
211
    fn next_byte(&mut self) -> Result<Option<u8>, ParseError>;
212

213
    /// Get current position in the input
214
    fn current_position(&self) -> usize;
215

216
    /// Begin string/key content processing at current position
217
    fn begin_string_content(&mut self, pos: usize);
218

219
    /// Get mutable access to parser state
220
    fn parser_state_mut(&mut self) -> &mut State;
221

222
    /// Get mutable access to the Unicode escape collector
223
    /// This eliminates the need for wrapper methods that just forward calls
224
    fn unicode_escape_collector_mut(&mut self) -> &mut UnicodeEscapeCollector;
225

226
    /// Extract string content using parser-specific logic
227
    fn extract_string_content(&mut self, start_pos: usize) -> Result<Event<'_, '_>, ParseError>;
228

229
    /// Extract key content using parser-specific logic
230
    fn extract_key_content(&mut self, start_pos: usize) -> Result<Event<'_, '_>, ParseError>;
231

232
    /// Extract a completed number using shared number parsing logic
233
    ///
234
    /// # Arguments
235
    /// * `start_pos` - Position where the number started
236
    /// * `from_container_end` - True if number was terminated by container delimiter
237
    /// * `finished` - True if the parser has finished processing input (StreamParser-specific)
238
    fn extract_number(
239
        &mut self,
240
        start_pos: usize,
241
        from_container_end: bool,
242
        finished: bool,
243
    ) -> Result<Event<'_, '_>, ParseError>;
244

245
    /// Shared validation and extraction for string content
246
    fn validate_and_extract_string(&mut self) -> Result<Event<'_, '_>, ParseError> {
431✔
247
        let start_pos = match *self.parser_state() {
431✔
248
            State::String(pos) => pos,
431✔
UNCOV
249
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
250
        };
251

252
        // Check for incomplete surrogate pairs before ending the string
253
        if self
431✔
254
            .unicode_escape_collector_mut()
431✔
255
            .has_pending_high_surrogate()
431✔
256
        {
257
            return Err(ParseError::InvalidUnicodeCodepoint);
3✔
258
        }
428✔
259

260
        *self.parser_state_mut() = State::None;
428✔
261
        self.extract_string_content(start_pos)
428✔
262
    }
431✔
263

264
    /// Shared validation and extraction for key content
265
    fn validate_and_extract_key(&mut self) -> Result<Event<'_, '_>, ParseError> {
325✔
266
        let start_pos = match *self.parser_state() {
325✔
267
            State::Key(pos) => pos,
325✔
268
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
269
        };
270

271
        // Check for incomplete surrogate pairs before ending the key
272
        if self
325✔
273
            .unicode_escape_collector_mut()
325✔
274
            .has_pending_high_surrogate()
325✔
275
        {
UNCOV
276
            return Err(ParseError::InvalidUnicodeCodepoint);
×
277
        }
325✔
278

279
        *self.parser_state_mut() = State::None;
325✔
280
        self.extract_key_content(start_pos)
325✔
281
    }
325✔
282

283
    /// Shared validation and extraction for number content
284
    fn validate_and_extract_number(
27✔
285
        &mut self,
27✔
286
        from_container_end: bool,
27✔
287
    ) -> Result<Event<'_, '_>, ParseError> {
27✔
288
        let start_pos = match *self.parser_state() {
27✔
289
            State::Number(pos) => pos,
27✔
UNCOV
290
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
291
        };
292

293
        *self.parser_state_mut() = State::None;
27✔
294
        self.extract_number(start_pos, from_container_end, true)
27✔
295
    }
27✔
296

297
    /// Get the current parser state for escape context checking
298
    fn parser_state(&self) -> &State;
299

300
    /// Process Unicode escape sequence using shared collector logic
301
    fn process_unicode_escape_with_collector(&mut self) -> Result<(), ParseError>;
302

303
    /// Handle a simple escape character (after EscapeProcessor conversion)
304
    fn handle_simple_escape_char(&mut self, escape_char: u8) -> Result<(), ParseError>;
305

306
    /// Begin escape sequence processing (lifecycle method with default no-op implementation)
307
    /// Called when escape sequence processing begins (e.g., on Begin(EscapeSequence))
308
    fn begin_escape_sequence(&mut self) -> Result<(), ParseError>;
309

310
    /// Begin unicode escape sequence processing
311
    fn begin_unicode_escape(&mut self) -> Result<(), ParseError>;
312

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

333
            // Number Begin events - identical logic
334
            ujson::Event::Begin(
335
                EventToken::Number | EventToken::NumberAndArray | EventToken::NumberAndObject,
336
            ) => {
337
                let pos = self.current_position();
471✔
338
                let number_start = ContentRange::number_start_from_current(pos);
471✔
339
                *self.parser_state_mut() = State::Number(number_start);
471✔
340
                Some(EventResult::Continue)
471✔
341
            }
342

343
            // Primitive Begin events - identical logic
344
            ujson::Event::Begin(EventToken::True | EventToken::False | EventToken::Null) => {
345
                Some(EventResult::Continue)
25✔
346
            }
347

348
            _ => None,
2,746✔
349
        }
350
    }
4,366✔
351

352
    /// Process Begin(EscapeSequence) events using the enhanced lifecycle interface
353
    fn process_begin_escape_sequence_event(&mut self) -> Result<(), ParseError> {
951✔
354
        // Only process if we're inside a string or key
355
        match self.parser_state() {
951✔
356
            State::String(_) | State::Key(_) => {
357
                self.begin_escape_sequence()?;
951✔
358
            }
UNCOV
359
            _ => {} // Ignore if not in string/key context
×
360
        }
361
        Ok(())
951✔
362
    }
951✔
363

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

370
        // Use unified escape token processing from EscapeProcessor
371
        let unescaped_char = EscapeProcessor::process_escape_token(escape_token)?;
688✔
372

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

381
        Ok(())
686✔
382
    }
688✔
383

384
    /// Process Unicode escape begin/end events that have similar patterns between parsers
385
    fn process_unicode_escape_events(&mut self, event: &ujson::Event) -> Result<bool, ParseError> {
420✔
386
        match event {
219✔
387
            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
                    State::String(_) | State::Key(_) => {
392
                        self.unicode_escape_collector_mut().reset();
219✔
393
                        self.begin_unicode_escape()?;
219✔
394
                    }
UNCOV
395
                    _ => {} // Ignore if not in string/key context
×
396
                }
397
                Ok(true) // Event was handled
219✔
398
            }
399
            ujson::Event::End(EventToken::UnicodeEscape) => {
400
                // Handle end of Unicode escape sequence (\uXXXX)
401
                match self.parser_state() {
201✔
402
                    State::String(_) | State::Key(_) => {
403
                        self.process_unicode_escape_with_collector()?;
201✔
404
                    }
UNCOV
405
                    _ => {} // Ignore if not in string/key context
×
406
                }
407
                Ok(true) // Event was handled
177✔
408
            }
UNCOV
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<ujson::Event>; 2]) {
20,427✔
416
    event_storage[0] = None;
20,427✔
417
    event_storage[1] = None;
20,427✔
418
}
20,427✔
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<ujson::Event>; 2],
20,952✔
426
) -> impl FnMut(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<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(event_storage: &mut [Option<ujson::Event>; 2]) -> Option<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: &ujson::Event) -> Option<EventResult<'static, 'static>> {
8,555✔
449
    match event {
1,947✔
450
        // Container events - identical processing
451
        ujson::Event::ObjectStart => Some(EventResult::Complete(Event::StartObject)),
251✔
452
        ujson::Event::ObjectEnd => Some(EventResult::Complete(Event::EndObject)),
206✔
453
        ujson::Event::ArrayStart => Some(EventResult::Complete(Event::StartArray)),
1,670✔
454
        ujson::Event::ArrayEnd => Some(EventResult::Complete(Event::EndArray)),
1,007✔
455

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

461
        // Content extraction triggers - identical logic
462
        ujson::Event::End(EventToken::String) => Some(EventResult::ExtractString),
432✔
463
        ujson::Event::End(EventToken::Key) => Some(EventResult::ExtractKey),
325✔
464
        ujson::Event::End(EventToken::Number) => Some(EventResult::ExtractNumber(false)),
56✔
465
        ujson::Event::End(EventToken::NumberAndArray) => Some(EventResult::ExtractNumber(true)),
140✔
466
        ujson::Event::End(EventToken::NumberAndObject) => Some(EventResult::ExtractNumber(true)),
83✔
467

468
        // All other events need parser-specific handling
469
        _ => None,
4,361✔
470
    }
471
}
8,555✔
472

473
#[cfg(test)]
474
mod tests {
475
    use super::*;
476

477
    #[test]
478
    fn test_container_events() {
1✔
479
        assert!(matches!(
1✔
480
            process_simple_events(&ujson::Event::ObjectStart),
1✔
481
            Some(EventResult::Complete(Event::StartObject))
482
        ));
483

484
        assert!(matches!(
1✔
485
            process_simple_events(&ujson::Event::ArrayEnd),
1✔
486
            Some(EventResult::Complete(Event::EndArray))
487
        ));
488
    }
1✔
489

490
    #[test]
491
    fn test_primitive_events() {
1✔
492
        assert!(matches!(
1✔
493
            process_simple_events(&ujson::Event::End(EventToken::True)),
1✔
494
            Some(EventResult::Complete(Event::Bool(true)))
495
        ));
496

497
        assert!(matches!(
1✔
498
            process_simple_events(&ujson::Event::End(EventToken::Null)),
1✔
499
            Some(EventResult::Complete(Event::Null))
500
        ));
501
    }
1✔
502

503
    #[test]
504
    fn test_extraction_triggers() {
1✔
505
        assert!(matches!(
1✔
506
            process_simple_events(&ujson::Event::End(EventToken::String)),
1✔
507
            Some(EventResult::ExtractString)
508
        ));
509

510
        assert!(matches!(
1✔
511
            process_simple_events(&ujson::Event::End(EventToken::Number)),
1✔
512
            Some(EventResult::ExtractNumber(false))
513
        ));
514

515
        assert!(matches!(
1✔
516
            process_simple_events(&ujson::Event::End(EventToken::NumberAndArray)),
1✔
517
            Some(EventResult::ExtractNumber(true))
518
        ));
519
    }
1✔
520

521
    #[test]
522
    fn test_complex_events_not_handled() {
1✔
523
        assert!(process_simple_events(&ujson::Event::Begin(EventToken::String)).is_none());
1✔
524
        assert!(process_simple_events(&ujson::Event::Begin(EventToken::EscapeQuote)).is_none());
1✔
525
    }
1✔
526

527
    // Mock ContentExtractor for testing
528
    struct MockContentExtractor {
529
        position: usize,
530
        state: State,
531
        string_begin_calls: Vec<usize>,
532
    }
533

534
    impl MockContentExtractor {
535
        fn new() -> Self {
5✔
536
            Self {
5✔
537
                position: 42,
5✔
538
                state: State::None,
5✔
539
                string_begin_calls: Vec::new(),
5✔
540
            }
5✔
541
        }
5✔
542
    }
543

544
    impl ContentExtractor for MockContentExtractor {
545
        fn next_byte(&mut self) -> Result<Option<u8>, ParseError> {
×
546
            Ok(None)
×
547
        }
×
548

549
        fn current_position(&self) -> usize {
3✔
550
            self.position
3✔
551
        }
3✔
552

553
        fn begin_string_content(&mut self, pos: usize) {
2✔
554
            self.string_begin_calls.push(pos);
2✔
555
        }
2✔
556

557
        fn parser_state_mut(&mut self) -> &mut State {
3✔
558
            &mut self.state
3✔
559
        }
3✔
560

UNCOV
561
        fn unicode_escape_collector_mut(&mut self) -> &mut UnicodeEscapeCollector {
×
UNCOV
562
            unimplemented!("Mock doesn't need unicode collector")
×
563
        }
564

565
        fn extract_string_content(
×
UNCOV
566
            &mut self,
×
567
            _start_pos: usize,
×
568
        ) -> Result<Event<'_, '_>, ParseError> {
×
569
            unimplemented!("Mock doesn't need extraction")
×
570
        }
571

572
        fn extract_key_content(&mut self, _start_pos: usize) -> Result<Event<'_, '_>, ParseError> {
×
573
            unimplemented!("Mock doesn't need extraction")
×
574
        }
575

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

UNCOV
585
        fn parser_state(&self) -> &State {
×
UNCOV
586
            &self.state
×
UNCOV
587
        }
×
588

UNCOV
589
        fn process_unicode_escape_with_collector(&mut self) -> Result<(), ParseError> {
×
UNCOV
590
            Ok(())
×
UNCOV
591
        }
×
592

UNCOV
593
        fn handle_simple_escape_char(&mut self, _escape_char: u8) -> Result<(), ParseError> {
×
UNCOV
594
            Ok(())
×
UNCOV
595
        }
×
596

UNCOV
597
        fn begin_unicode_escape(&mut self) -> Result<(), ParseError> {
×
UNCOV
598
            Ok(())
×
UNCOV
599
        }
×
600

UNCOV
601
        fn begin_escape_sequence(&mut self) -> Result<(), ParseError> {
×
UNCOV
602
            Ok(())
×
UNCOV
603
        }
×
604
    }
605

606
    #[test]
607
    fn test_begin_events_key() {
1✔
608
        let mut context = MockContentExtractor::new();
1✔
609
        let event = ujson::Event::Begin(EventToken::Key);
1✔
610

611
        let result = context.process_begin_events(&event);
1✔
612

613
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
614
        assert!(matches!(context.state, State::Key(42)));
1✔
615
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
616
    }
1✔
617

618
    #[test]
619
    fn test_begin_events_string() {
1✔
620
        let mut context = MockContentExtractor::new();
1✔
621
        let event = ujson::Event::Begin(EventToken::String);
1✔
622

623
        let result = context.process_begin_events(&event);
1✔
624

625
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
626
        assert!(matches!(context.state, State::String(42)));
1✔
627
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
628
    }
1✔
629

630
    #[test]
631
    fn test_begin_events_number() {
1✔
632
        let mut context = MockContentExtractor::new();
1✔
633
        let event = ujson::Event::Begin(EventToken::Number);
1✔
634

635
        let result = context.process_begin_events(&event);
1✔
636

637
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
638
        // Number should get position adjusted by ContentRange::number_start_from_current
639
        assert!(matches!(context.state, State::Number(_)));
1✔
640
        assert_eq!(context.string_begin_calls, Vec::<usize>::new()); // No string calls for numbers
1✔
641
    }
1✔
642

643
    #[test]
644
    fn test_begin_events_primitives() {
1✔
645
        let mut context = MockContentExtractor::new();
1✔
646

647
        for token in [EventToken::True, EventToken::False, EventToken::Null] {
3✔
648
            let event = ujson::Event::Begin(token);
3✔
649
            let result = context.process_begin_events(&event);
3✔
650
            assert!(matches!(result, Some(EventResult::Continue)));
3✔
651
        }
652

653
        // Should not affect state or string processing
654
        assert!(matches!(context.state, State::None));
1✔
655
        assert!(context.string_begin_calls.is_empty());
1✔
656
    }
1✔
657

658
    #[test]
659
    fn test_begin_events_not_handled() {
1✔
660
        let mut context = MockContentExtractor::new();
1✔
661
        let event = ujson::Event::Begin(EventToken::EscapeQuote);
1✔
662

663
        let result = context.process_begin_events(&event);
1✔
664

665
        assert!(result.is_none());
1✔
666
        assert!(matches!(context.state, State::None));
1✔
667
        assert!(context.string_begin_calls.is_empty());
1✔
668
    }
1✔
669

670
    #[test]
671
    fn test_tokenizer_callback() {
1✔
672
        let mut event_storage = [None, None];
1✔
673

674
        // Initially no events
675
        assert!(!have_events(&event_storage));
1✔
676

677
        {
1✔
678
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
679

1✔
680
            // Add first event
1✔
681
            callback(ujson::Event::ObjectStart, 1);
1✔
682
        }
1✔
683
        assert!(have_events(&event_storage));
1✔
684
        assert!(event_storage[0].is_some());
1✔
685
        assert!(event_storage[1].is_none());
1✔
686

687
        {
1✔
688
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
689
            // Add second event
1✔
690
            callback(ujson::Event::ArrayStart, 1);
1✔
691
        }
1✔
692
        assert!(event_storage[0].is_some());
1✔
693
        assert!(event_storage[1].is_some());
1✔
694

695
        {
1✔
696
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
697
            // Storage is full, third event should be ignored (no panic)
1✔
698
            callback(ujson::Event::ObjectEnd, 1);
1✔
699
        }
1✔
700
        assert!(event_storage[0].is_some());
1✔
701
        assert!(event_storage[1].is_some());
1✔
702
    }
1✔
703

704
    #[test]
705
    fn test_event_extraction() {
1✔
706
        let mut event_storage = [
1✔
707
            Some(ujson::Event::ObjectStart),
1✔
708
            Some(ujson::Event::ArrayStart),
1✔
709
        ];
1✔
710

711
        // Extract first event
712
        let first = take_first_event(&mut event_storage);
1✔
713
        assert!(matches!(first, Some(ujson::Event::ObjectStart)));
1✔
714
        assert!(event_storage[0].is_none());
1✔
715
        assert!(event_storage[1].is_some());
1✔
716

717
        // Extract second event
718
        let second = take_first_event(&mut event_storage);
1✔
719
        assert!(matches!(second, Some(ujson::Event::ArrayStart)));
1✔
720
        assert!(event_storage[0].is_none());
1✔
721
        assert!(event_storage[1].is_none());
1✔
722

723
        // No more events
724
        let none = take_first_event(&mut event_storage);
1✔
725
        assert!(none.is_none());
1✔
726
        assert!(!have_events(&event_storage));
1✔
727
    }
1✔
728
}
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