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

kaidokert / picojson-rs / 16708793874

03 Aug 2025 07:48PM UTC coverage: 93.313% (-0.7%) from 94.008%
16708793874

Pull #77

github

web-flow
Merge 6f7b76718 into 377ce19f7
Pull Request #77: Datasource intro

483 of 578 new or added lines in 9 files covered. (83.56%)

63 existing lines in 6 files now uncovered.

4996 of 5354 relevant lines covered (93.31%)

1311.46 hits per line

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

86.7
/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
    /// Whether this parser handles chunked input (true for PushParser, false for Slice/Stream)
26
    /// When true, running out of input returns EndOfData. When false, calls tokenizer.finish().
27
    handles_chunked_input: bool,
28
}
29

30
impl<T: ujson::BitBucket, C: ujson::DepthCounter> ParserCore<T, C> {
31
    /// Create a new ParserCore for non-chunked parsers (SliceParser, StreamParser)
32
    pub fn new() -> Self {
1,134✔
33
        Self {
1,134✔
34
            tokenizer: Tokenizer::new(),
1,134✔
35
            parser_state: ParserState::new(),
1,134✔
36
            in_escape_sequence: false,
1,134✔
37
            handles_chunked_input: false,
1,134✔
38
        }
1,134✔
39
    }
1,134✔
40

41
    /// Create a new ParserCore for chunked parsers (PushParser)
42
    pub fn new_chunked() -> Self {
1,061✔
43
        Self {
1,061✔
44
            tokenizer: Tokenizer::new(),
1,061✔
45
            parser_state: ParserState::new(),
1,061✔
46
            in_escape_sequence: false,
1,061✔
47
            handles_chunked_input: true,
1,061✔
48
        }
1,061✔
49
    }
1,061✔
50

51
    /// Check if currently in an escape sequence
UNCOV
52
    pub fn in_escape_sequence(&self) -> bool {
×
UNCOV
53
        self.in_escape_sequence
×
UNCOV
54
    }
×
55

56
    /// Unified implementation with optional byte accumulation callback.
57
    /// This supports StreamParser-specific byte accumulation when no events are generated.
58
    /// SliceParser passes a no-op closure for byte_accumulator.
59
    pub fn next_event_impl<'a, P, F>(
5,272✔
60
        &mut self,
5,272✔
61
        provider: &'a mut P,
5,272✔
62
        escape_timing: EscapeTiming,
5,272✔
63
        byte_accumulator: F,
5,272✔
64
    ) -> Result<Event<'a, 'a>, ParseError>
5,272✔
65
    where
5,272✔
66
        P: ContentExtractor,
5,272✔
67
        F: FnMut(&mut P, u8) -> Result<(), ParseError>,
5,272✔
68
    {
69
        self.next_event_impl_with_flags(provider, escape_timing, byte_accumulator, false)
5,272✔
70
    }
5,272✔
71

72
    /// Extended version with flags for specialized behavior
73
    pub fn next_event_impl_with_flags<'a, P, F>(
18,685✔
74
        &mut self,
18,685✔
75
        provider: &'a mut P,
18,685✔
76
        escape_timing: EscapeTiming,
18,685✔
77
        mut byte_accumulator: F,
18,685✔
78
        always_accumulate_during_escapes: bool,
18,685✔
79
    ) -> Result<Event<'a, 'a>, ParseError>
18,685✔
80
    where
18,685✔
81
        P: ContentExtractor,
18,685✔
82
        F: FnMut(&mut P, u8) -> Result<(), ParseError>,
18,685✔
83
    {
84
        loop {
85
            while !have_events(&self.parser_state.evts) {
77,601✔
86
                if let Some(byte) = provider.next_byte()? {
56,527✔
87
                    {
88
                        clear_events(&mut self.parser_state.evts);
49,056✔
89
                        let mut callback = create_tokenizer_callback(&mut self.parser_state.evts);
49,056✔
90
                        self.tokenizer
49,056✔
91
                            .parse_chunk(&[byte], &mut callback)
49,056✔
92
                            .map_err(ParseError::TokenizerError)?;
49,056✔
93
                    }
94

95
                    // Call byte accumulator if no events were generated AND we're not in an escape sequence
96
                    // OR if we're configured to always accumulate during escape sequences (for PushParser)
97
                    // OR if we always accumulate during escapes AND we're processing Unicode escape hex digits
98
                    let should_accumulate = if always_accumulate_during_escapes {
49,041✔
99
                        // For PushParser: accumulate during escapes even when events are generated
100
                        // This ensures hex digits reach the accumulator even when End UnicodeEscape events consume them
101
                        // BUT still respect the normal logic when not in escape sequences
102
                        if self.in_escape_sequence {
28,509✔
103
                            true // Always accumulate during escape sequences
2,964✔
104
                        } else {
105
                            !have_events(&self.parser_state.evts) // Normal behavior outside escapes
25,545✔
106
                        }
107
                    } else {
108
                        // For other parsers: only accumulate when no events generated and not in escape
109
                        !have_events(&self.parser_state.evts) && !self.in_escape_sequence
20,532✔
110
                    };
111

112
                    if should_accumulate {
49,041✔
113
                        byte_accumulator(provider, byte)?;
30,880✔
114
                    }
18,161✔
115
                } else {
116
                    // Handle end of input - behavior depends on parser type
117
                    if self.handles_chunked_input {
6,940✔
118
                        // For chunked parsers (PushParser), return EndOfData so they can handle chunk boundaries
119
                        return Err(ParseError::EndOfData);
6,418✔
120
                    } else {
121
                        // For non-chunked parsers (SliceParser, StreamParser), finish the document
122
                        {
123
                            let mut finish_callback =
522✔
124
                                create_tokenizer_callback(&mut self.parser_state.evts);
522✔
125
                            let _bytes_processed = self.tokenizer.finish(&mut finish_callback)?;
522✔
126
                        } // Drop the callback to release the borrow
127

128
                        // If finish() generated events, process them. Otherwise, return EndDocument.
129
                        if !have_events(&self.parser_state.evts) {
518✔
130
                            return Ok(Event::EndDocument);
514✔
131
                        }
4✔
132
                        // Continue to process any events generated by finish()
133
                    }
134
                }
135
            }
136

137
            let taken_event = take_first_event(&mut self.parser_state.evts);
21,074✔
138
            let Some(taken) = taken_event else {
21,074✔
UNCOV
139
                return Err(UnexpectedState::StateMismatch.into());
×
140
            };
141

142
            // Try shared event processors first
143
            if let Some(result) =
15,110✔
144
                process_simple_events(&taken).or_else(|| provider.process_begin_events(&taken))
21,074✔
145
            {
146
                match result {
15,110✔
147
                    EventResult::Complete(event) => return Ok(event),
7,754✔
148
                    EventResult::ExtractString => return provider.validate_and_extract_string(),
1,289✔
149
                    EventResult::ExtractKey => return provider.validate_and_extract_key(),
1,229✔
150
                    EventResult::ExtractNumber(from_container_end) => {
815✔
151
                        return provider.validate_and_extract_number(from_container_end)
815✔
152
                    }
153
                    EventResult::Continue => continue,
4,023✔
154
                }
155
            }
5,964✔
156

157
            // Handle parser-specific events based on escape timing
158
            match taken {
252✔
159
                ujson::Event::Begin(EventToken::EscapeSequence) => {
160
                    self.in_escape_sequence = true;
2,044✔
161
                    provider.process_begin_escape_sequence_event()?;
2,044✔
162
                }
163
                ujson::Event::Begin(EventToken::UnicodeEscape) => {
164
                    self.in_escape_sequence = true;
685✔
165
                    provider.process_unicode_escape_events(&taken)?;
685✔
166
                }
167
                ujson::Event::End(EventToken::UnicodeEscape) => {
168
                    self.in_escape_sequence = false;
655✔
169
                    provider.process_unicode_escape_events(&taken)?;
655✔
170
                }
171
                ujson::Event::Begin(
172
                    escape_token @ (EventToken::EscapeQuote
1✔
173
                    | EventToken::EscapeBackslash
174
                    | EventToken::EscapeSlash
175
                    | EventToken::EscapeBackspace
176
                    | EventToken::EscapeFormFeed
177
                    | EventToken::EscapeNewline
178
                    | EventToken::EscapeCarriageReturn
179
                    | EventToken::EscapeTab),
180
                ) if escape_timing == EscapeTiming::OnBegin => {
252✔
181
                    // For SliceParser, the escape is handled in a single event.
182
                    // It begins and ends within this block.
183
                    self.in_escape_sequence = true;
20✔
184
                    provider.process_simple_escape_event(&escape_token)?;
20✔
185
                    self.in_escape_sequence = false;
18✔
186
                }
187
                ujson::Event::End(
188
                    escape_token @ (EventToken::EscapeQuote
251✔
189
                    | EventToken::EscapeBackslash
190
                    | EventToken::EscapeSlash
191
                    | EventToken::EscapeBackspace
192
                    | EventToken::EscapeFormFeed
193
                    | EventToken::EscapeNewline
194
                    | EventToken::EscapeCarriageReturn
195
                    | EventToken::EscapeTab),
196
                ) if escape_timing == EscapeTiming::OnEnd => {
252✔
197
                    // For StreamParser, the escape ends here.
198
                    provider.process_simple_escape_event(&escape_token)?;
1,271✔
199
                    self.in_escape_sequence = false;
1,259✔
200
                }
201
                _ => {
1,289✔
202
                    // All other events continue to next iteration
1,289✔
203
                }
1,289✔
204
            }
205
        }
206
    }
18,685✔
207
}
208

209
impl<T: ujson::BitBucket, C: ujson::DepthCounter> Default for ParserCore<T, C> {
UNCOV
210
    fn default() -> Self {
×
UNCOV
211
        Self::new()
×
UNCOV
212
    }
×
213
}
214

215
/// Enum to specify when escape sequences should be processed
216
#[derive(Debug, Clone, Copy, PartialEq)]
217
pub enum EscapeTiming {
218
    /// Process simple escape sequences on Begin events (SliceParser)
219
    OnBegin,
220
    /// Process simple escape sequences on End events (StreamParser)
221
    OnEnd,
222
}
223

224
/// Result of processing a tokenizer event
225
#[derive(Debug)]
226
pub enum EventResult<'a, 'b> {
227
    /// Event processing is complete, return this event to the user
228
    Complete(Event<'a, 'b>),
229
    /// Continue processing more tokenizer events
230
    Continue,
231
    /// Extract string content (delegate to parser-specific logic)
232
    ExtractString,
233
    /// Extract key content (delegate to parser-specific logic)
234
    ExtractKey,
235
    /// Extract number content (delegate to parser-specific logic)
236
    /// bool indicates if number was terminated by container delimiter
237
    ExtractNumber(bool),
238
}
239

240
/// Trait for content extraction operations that differ between parsers
241
/// Consolidates ParserContext and ContentExtractor functionality
242
pub trait ContentExtractor {
243
    /// Get the next byte from the input source
244
    /// Returns None when end of input is reached
245
    fn next_byte(&mut self) -> Result<Option<u8>, ParseError>;
246

247
    /// Get current position in the input
248
    fn current_position(&self) -> usize;
249

250
    /// Begin string/key content processing at current position
251
    fn begin_string_content(&mut self, pos: usize);
252

253
    /// Get mutable access to parser state
254
    fn parser_state_mut(&mut self) -> &mut State;
255

256
    /// Get mutable access to the Unicode escape collector
257
    /// This eliminates the need for wrapper methods that just forward calls
258
    fn unicode_escape_collector_mut(&mut self) -> &mut UnicodeEscapeCollector;
259

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

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

266
    /// Extract a completed number using shared number parsing logic
267
    ///
268
    /// # Arguments
269
    /// * `start_pos` - Position where the number started
270
    /// * `from_container_end` - True if number was terminated by container delimiter
271
    /// * `finished` - True if the parser has finished processing input (StreamParser-specific)
272
    fn extract_number(
273
        &mut self,
274
        start_pos: usize,
275
        from_container_end: bool,
276
        finished: bool,
277
    ) -> Result<Event<'_, '_>, ParseError>;
278

279
    /// Shared validation and extraction for string content
280
    fn validate_and_extract_string(&mut self) -> Result<Event<'_, '_>, ParseError> {
1,289✔
281
        let start_pos = match *self.parser_state() {
1,289✔
282
            State::String(pos) => pos,
1,289✔
UNCOV
283
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
284
        };
285

286
        // Check for incomplete surrogate pairs before ending the string
287
        if self
1,289✔
288
            .unicode_escape_collector_mut()
1,289✔
289
            .has_pending_high_surrogate()
1,289✔
290
        {
291
            return Err(ParseError::InvalidUnicodeCodepoint);
3✔
292
        }
1,286✔
293

294
        *self.parser_state_mut() = State::None;
1,286✔
295
        self.extract_string_content(start_pos)
1,286✔
296
    }
1,289✔
297

298
    /// Shared validation and extraction for key content
299
    fn validate_and_extract_key(&mut self) -> Result<Event<'_, '_>, ParseError> {
1,229✔
300
        let start_pos = match *self.parser_state() {
1,229✔
301
            State::Key(pos) => pos,
1,229✔
UNCOV
302
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
303
        };
304

305
        // Check for incomplete surrogate pairs before ending the key
306
        if self
1,229✔
307
            .unicode_escape_collector_mut()
1,229✔
308
            .has_pending_high_surrogate()
1,229✔
309
        {
UNCOV
310
            return Err(ParseError::InvalidUnicodeCodepoint);
×
311
        }
1,229✔
312

313
        *self.parser_state_mut() = State::None;
1,229✔
314
        self.extract_key_content(start_pos)
1,229✔
315
    }
1,229✔
316

317
    /// Shared validation and extraction for number content
318
    fn validate_and_extract_number(
565✔
319
        &mut self,
565✔
320
        from_container_end: bool,
565✔
321
    ) -> Result<Event<'_, '_>, ParseError> {
565✔
322
        let start_pos = match *self.parser_state() {
565✔
323
            State::Number(pos) => pos,
565✔
UNCOV
324
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
325
        };
326

327
        *self.parser_state_mut() = State::None;
565✔
328
        self.extract_number(start_pos, from_container_end, true)
565✔
329
    }
565✔
330

331
    /// Get the current parser state for escape context checking
332
    fn parser_state(&self) -> &State;
333

334
    /// Process Unicode escape sequence using shared collector logic
335
    fn process_unicode_escape_with_collector(&mut self) -> Result<(), ParseError>;
336

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

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

344
    /// Begin unicode escape sequence processing
345
    fn begin_unicode_escape(&mut self) -> Result<(), ParseError>;
346

347
    /// Process Begin events that have similar patterns between parsers
348
    fn process_begin_events(
9,994✔
349
        &mut self,
9,994✔
350
        event: &ujson::Event,
9,994✔
351
    ) -> Option<EventResult<'static, 'static>> {
9,994✔
352
        match event {
8,050✔
353
            // String/Key Begin events - nearly identical patterns
354
            ujson::Event::Begin(EventToken::Key) => {
355
                let pos = self.current_position();
1,273✔
356
                *self.parser_state_mut() = State::Key(pos);
1,273✔
357
                self.begin_string_content(pos);
1,273✔
358
                Some(EventResult::Continue)
1,273✔
359
            }
360
            ujson::Event::Begin(EventToken::String) => {
361
                let pos = self.current_position();
1,700✔
362
                *self.parser_state_mut() = State::String(pos);
1,700✔
363
                self.begin_string_content(pos);
1,700✔
364
                Some(EventResult::Continue)
1,700✔
365
            }
366

367
            // Number Begin events - identical logic
368
            ujson::Event::Begin(
369
                EventToken::Number | EventToken::NumberAndArray | EventToken::NumberAndObject,
370
            ) => {
371
                let pos = self.current_position();
1,027✔
372
                let number_start = ContentRange::number_start_from_current(pos);
1,027✔
373
                *self.parser_state_mut() = State::Number(number_start);
1,027✔
374
                Some(EventResult::Continue)
1,027✔
375
            }
376

377
            // Primitive Begin events - identical logic
378
            ujson::Event::Begin(EventToken::True | EventToken::False | EventToken::Null) => {
379
                Some(EventResult::Continue)
29✔
380
            }
381

382
            _ => None,
5,965✔
383
        }
384
    }
9,994✔
385

386
    /// Process Begin(EscapeSequence) events using the enhanced lifecycle interface
387
    fn process_begin_escape_sequence_event(&mut self) -> Result<(), ParseError> {
2,044✔
388
        // Only process if we're inside a string or key
389
        match self.parser_state() {
2,044✔
390
            State::String(_) | State::Key(_) => {
391
                self.begin_escape_sequence()?;
2,044✔
392
            }
UNCOV
393
            _ => {} // Ignore if not in string/key context
×
394
        }
395
        Ok(())
2,038✔
396
    }
2,044✔
397

398
    /// Process simple escape sequence events that have similar patterns between parsers
399
    fn process_simple_escape_event(&mut self, escape_token: &EventToken) -> Result<(), ParseError> {
1,291✔
400
        // Clear any pending high surrogate state when we encounter a simple escape
401
        // This ensures that interrupted surrogate pairs (like \uD801\n\uDC37) are properly rejected
402
        self.unicode_escape_collector_mut().reset_all();
1,291✔
403

404
        // Use unified escape token processing from EscapeProcessor
405
        let unescaped_char = EscapeProcessor::process_escape_token(escape_token)?;
1,291✔
406

407
        // Only process if we're inside a string or key
408
        match self.parser_state() {
1,291✔
409
            State::String(_) | State::Key(_) => {
410
                self.handle_simple_escape_char(unescaped_char)?;
1,291✔
411
            }
UNCOV
412
            _ => {} // Ignore if not in string/key context
×
413
        }
414

415
        Ok(())
1,277✔
416
    }
1,291✔
417

418
    /// Process Unicode escape begin/end events that have similar patterns between parsers
419
    fn process_unicode_escape_events(&mut self, event: &ujson::Event) -> Result<bool, ParseError> {
1,340✔
420
        match event {
685✔
421
            ujson::Event::Begin(EventToken::UnicodeEscape) => {
422
                // Start Unicode escape collection - reset collector for new sequence
423
                // Only handle if we're inside a string or key
424
                match self.parser_state() {
685✔
425
                    State::String(_) | State::Key(_) => {
426
                        self.unicode_escape_collector_mut().reset();
685✔
427
                        self.begin_unicode_escape()?;
685✔
428
                    }
UNCOV
429
                    _ => {}
×
430
                }
431
                Ok(true) // Event was handled
685✔
432
            }
433
            ujson::Event::End(EventToken::UnicodeEscape) => {
434
                // Handle end of Unicode escape sequence (\uXXXX)
435
                match self.parser_state() {
655✔
436
                    State::String(_) | State::Key(_) => {
437
                        self.process_unicode_escape_with_collector()?;
655✔
438
                    }
UNCOV
439
                    _ => {}
×
440
                }
441
                Ok(true) // Event was handled
631✔
442
            }
UNCOV
443
            _ => Ok(false), // Event was not handled
×
444
        }
445
    }
1,340✔
446
}
447

448
/// Clear event storage array - utility function
449
pub fn clear_events(event_storage: &mut [Option<ujson::Event>; 2]) {
49,056✔
450
    event_storage[0] = None;
49,056✔
451
    event_storage[1] = None;
49,056✔
452
}
49,056✔
453

454
/// Creates a standard tokenizer callback for event storage
455
///
456
/// This callback stores tokenizer events in the parser's event array, filling the first
457
/// available slot. This pattern is identical across both SliceParser and StreamParser.
458
pub fn create_tokenizer_callback(
49,581✔
459
    event_storage: &mut [Option<ujson::Event>; 2],
49,581✔
460
) -> impl FnMut(ujson::Event, usize) + '_ {
49,581✔
461
    |event, _len| {
21,113✔
462
        for evt in event_storage.iter_mut() {
23,157✔
463
            if evt.is_none() {
23,157✔
464
                *evt = Some(event);
21,112✔
465
                return;
21,112✔
466
            }
2,045✔
467
        }
468
    }
21,113✔
469
}
49,581✔
470

471
/// Shared utility to check if any events are waiting to be processed
472
pub fn have_events(event_storage: &[Option<ujson::Event>; 2]) -> bool {
124,199✔
473
    event_storage.iter().any(|evt| evt.is_some())
211,836✔
474
}
124,199✔
475

476
/// Shared utility to extract the first available event from storage
477
pub fn take_first_event(event_storage: &mut [Option<ujson::Event>; 2]) -> Option<ujson::Event> {
21,077✔
478
    event_storage.iter_mut().find_map(|e| e.take())
23,111✔
479
}
21,077✔
480

481
/// Process simple container and primitive events that are identical between parsers
482
pub fn process_simple_events(event: &ujson::Event) -> Option<EventResult<'static, 'static>> {
21,083✔
483
    match event {
5,308✔
484
        // Container events - identical processing
485
        ujson::Event::ObjectStart => Some(EventResult::Complete(Event::StartObject)),
826✔
486
        ujson::Event::ObjectEnd => Some(EventResult::Complete(Event::EndObject)),
757✔
487
        ujson::Event::ArrayStart => Some(EventResult::Complete(Event::StartArray)),
3,478✔
488
        ujson::Event::ArrayEnd => Some(EventResult::Complete(Event::EndArray)),
2,669✔
489

490
        // Primitive values - identical processing
491
        ujson::Event::End(EventToken::True) => Some(EventResult::Complete(Event::Bool(true))),
13✔
492
        ujson::Event::End(EventToken::False) => Some(EventResult::Complete(Event::Bool(false))),
7✔
493
        ujson::Event::End(EventToken::Null) => Some(EventResult::Complete(Event::Null)),
8✔
494

495
        // Content extraction triggers - identical logic
496
        ujson::Event::End(EventToken::String) => Some(EventResult::ExtractString),
1,290✔
497
        ujson::Event::End(EventToken::Key) => Some(EventResult::ExtractKey),
1,229✔
498
        ujson::Event::End(EventToken::Number) => Some(EventResult::ExtractNumber(false)),
65✔
499
        ujson::Event::End(EventToken::NumberAndArray) => Some(EventResult::ExtractNumber(true)),
348✔
500
        ujson::Event::End(EventToken::NumberAndObject) => Some(EventResult::ExtractNumber(true)),
404✔
501

502
        // All other events need parser-specific handling
503
        _ => None,
9,989✔
504
    }
505
}
21,083✔
506

507
#[cfg(test)]
508
mod tests {
509
    use super::*;
510

511
    #[test]
512
    fn test_container_events() {
1✔
513
        assert!(matches!(
1✔
514
            process_simple_events(&ujson::Event::ObjectStart),
1✔
515
            Some(EventResult::Complete(Event::StartObject))
516
        ));
517

518
        assert!(matches!(
1✔
519
            process_simple_events(&ujson::Event::ArrayEnd),
1✔
520
            Some(EventResult::Complete(Event::EndArray))
521
        ));
522
    }
1✔
523

524
    #[test]
525
    fn test_primitive_events() {
1✔
526
        assert!(matches!(
1✔
527
            process_simple_events(&ujson::Event::End(EventToken::True)),
1✔
528
            Some(EventResult::Complete(Event::Bool(true)))
529
        ));
530

531
        assert!(matches!(
1✔
532
            process_simple_events(&ujson::Event::End(EventToken::Null)),
1✔
533
            Some(EventResult::Complete(Event::Null))
534
        ));
535
    }
1✔
536

537
    #[test]
538
    fn test_extraction_triggers() {
1✔
539
        assert!(matches!(
1✔
540
            process_simple_events(&ujson::Event::End(EventToken::String)),
1✔
541
            Some(EventResult::ExtractString)
542
        ));
543

544
        assert!(matches!(
1✔
545
            process_simple_events(&ujson::Event::End(EventToken::Number)),
1✔
546
            Some(EventResult::ExtractNumber(false))
547
        ));
548

549
        assert!(matches!(
1✔
550
            process_simple_events(&ujson::Event::End(EventToken::NumberAndArray)),
1✔
551
            Some(EventResult::ExtractNumber(true))
552
        ));
553
    }
1✔
554

555
    #[test]
556
    fn test_complex_events_not_handled() {
1✔
557
        assert!(process_simple_events(&ujson::Event::Begin(EventToken::String)).is_none());
1✔
558
        assert!(process_simple_events(&ujson::Event::Begin(EventToken::EscapeQuote)).is_none());
1✔
559
    }
1✔
560

561
    // Mock ContentExtractor for testing
562
    struct MockContentExtractor {
563
        position: usize,
564
        state: State,
565
        string_begin_calls: Vec<usize>,
566
    }
567

568
    impl MockContentExtractor {
569
        fn new() -> Self {
5✔
570
            Self {
5✔
571
                position: 42,
5✔
572
                state: State::None,
5✔
573
                string_begin_calls: Vec::new(),
5✔
574
            }
5✔
575
        }
5✔
576
    }
577

578
    impl ContentExtractor for MockContentExtractor {
UNCOV
579
        fn next_byte(&mut self) -> Result<Option<u8>, ParseError> {
×
UNCOV
580
            Ok(None)
×
UNCOV
581
        }
×
582

583
        fn current_position(&self) -> usize {
3✔
584
            self.position
3✔
585
        }
3✔
586

587
        fn begin_string_content(&mut self, pos: usize) {
2✔
588
            self.string_begin_calls.push(pos);
2✔
589
        }
2✔
590

591
        fn parser_state_mut(&mut self) -> &mut State {
3✔
592
            &mut self.state
3✔
593
        }
3✔
594

595
        fn unicode_escape_collector_mut(&mut self) -> &mut UnicodeEscapeCollector {
×
596
            unimplemented!("Mock doesn't need unicode collector")
×
597
        }
598

UNCOV
599
        fn extract_string_content(
×
UNCOV
600
            &mut self,
×
601
            _start_pos: usize,
×
602
        ) -> Result<Event<'_, '_>, ParseError> {
×
UNCOV
603
            unimplemented!("Mock doesn't need extraction")
×
604
        }
605

606
        fn extract_key_content(&mut self, _start_pos: usize) -> Result<Event<'_, '_>, ParseError> {
×
607
            unimplemented!("Mock doesn't need extraction")
×
608
        }
609

610
        fn extract_number(
×
611
            &mut self,
×
UNCOV
612
            _start_pos: usize,
×
UNCOV
613
            _from_container_end: bool,
×
614
            _finished: bool,
×
615
        ) -> Result<Event<'_, '_>, ParseError> {
×
616
            unimplemented!("Mock doesn't need extraction")
×
617
        }
618

619
        fn parser_state(&self) -> &State {
×
620
            &self.state
×
UNCOV
621
        }
×
622

623
        fn process_unicode_escape_with_collector(&mut self) -> Result<(), ParseError> {
×
624
            Ok(())
×
UNCOV
625
        }
×
626

627
        fn handle_simple_escape_char(&mut self, _escape_char: u8) -> Result<(), ParseError> {
×
628
            Ok(())
×
UNCOV
629
        }
×
630

631
        fn begin_unicode_escape(&mut self) -> Result<(), ParseError> {
×
632
            Ok(())
×
UNCOV
633
        }
×
634

UNCOV
635
        fn begin_escape_sequence(&mut self) -> Result<(), ParseError> {
×
UNCOV
636
            Ok(())
×
UNCOV
637
        }
×
638
    }
639

640
    #[test]
641
    fn test_begin_events_key() {
1✔
642
        let mut context = MockContentExtractor::new();
1✔
643
        let event = ujson::Event::Begin(EventToken::Key);
1✔
644

645
        let result = context.process_begin_events(&event);
1✔
646

647
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
648
        assert!(matches!(context.state, State::Key(42)));
1✔
649
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
650
    }
1✔
651

652
    #[test]
653
    fn test_begin_events_string() {
1✔
654
        let mut context = MockContentExtractor::new();
1✔
655
        let event = ujson::Event::Begin(EventToken::String);
1✔
656

657
        let result = context.process_begin_events(&event);
1✔
658

659
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
660
        assert!(matches!(context.state, State::String(42)));
1✔
661
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
662
    }
1✔
663

664
    #[test]
665
    fn test_begin_events_number() {
1✔
666
        let mut context = MockContentExtractor::new();
1✔
667
        let event = ujson::Event::Begin(EventToken::Number);
1✔
668

669
        let result = context.process_begin_events(&event);
1✔
670

671
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
672
        // Number should get position adjusted by ContentRange::number_start_from_current
673
        assert!(matches!(context.state, State::Number(_)));
1✔
674
        assert_eq!(context.string_begin_calls, Vec::<usize>::new()); // No string calls for numbers
1✔
675
    }
1✔
676

677
    #[test]
678
    fn test_begin_events_primitives() {
1✔
679
        let mut context = MockContentExtractor::new();
1✔
680

681
        for token in [EventToken::True, EventToken::False, EventToken::Null] {
3✔
682
            let event = ujson::Event::Begin(token);
3✔
683
            let result = context.process_begin_events(&event);
3✔
684
            assert!(matches!(result, Some(EventResult::Continue)));
3✔
685
        }
686

687
        // Should not affect state or string processing
688
        assert!(matches!(context.state, State::None));
1✔
689
        assert!(context.string_begin_calls.is_empty());
1✔
690
    }
1✔
691

692
    #[test]
693
    fn test_begin_events_not_handled() {
1✔
694
        let mut context = MockContentExtractor::new();
1✔
695
        let event = ujson::Event::Begin(EventToken::EscapeQuote);
1✔
696

697
        let result = context.process_begin_events(&event);
1✔
698

699
        assert!(result.is_none());
1✔
700
        assert!(matches!(context.state, State::None));
1✔
701
        assert!(context.string_begin_calls.is_empty());
1✔
702
    }
1✔
703

704
    #[test]
705
    fn test_tokenizer_callback() {
1✔
706
        let mut event_storage = [None, None];
1✔
707

708
        // Initially no events
709
        assert!(!have_events(&event_storage));
1✔
710

711
        {
1✔
712
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
713

1✔
714
            // Add first event
1✔
715
            callback(ujson::Event::ObjectStart, 1);
1✔
716
        }
1✔
717
        assert!(have_events(&event_storage));
1✔
718
        assert!(event_storage[0].is_some());
1✔
719
        assert!(event_storage[1].is_none());
1✔
720

721
        {
1✔
722
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
723
            // Add second event
1✔
724
            callback(ujson::Event::ArrayStart, 1);
1✔
725
        }
1✔
726
        assert!(event_storage[0].is_some());
1✔
727
        assert!(event_storage[1].is_some());
1✔
728

729
        {
1✔
730
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
731
            // Storage is full, third event should be ignored (no panic)
1✔
732
            callback(ujson::Event::ObjectEnd, 1);
1✔
733
        }
1✔
734
        assert!(event_storage[0].is_some());
1✔
735
        assert!(event_storage[1].is_some());
1✔
736
    }
1✔
737

738
    #[test]
739
    fn test_event_extraction() {
1✔
740
        let mut event_storage = [
1✔
741
            Some(ujson::Event::ObjectStart),
1✔
742
            Some(ujson::Event::ArrayStart),
1✔
743
        ];
1✔
744

745
        // Extract first event
746
        let first = take_first_event(&mut event_storage);
1✔
747
        assert!(matches!(first, Some(ujson::Event::ObjectStart)));
1✔
748
        assert!(event_storage[0].is_none());
1✔
749
        assert!(event_storage[1].is_some());
1✔
750

751
        // Extract second event
752
        let second = take_first_event(&mut event_storage);
1✔
753
        assert!(matches!(second, Some(ujson::Event::ArrayStart)));
1✔
754
        assert!(event_storage[0].is_none());
1✔
755
        assert!(event_storage[1].is_none());
1✔
756

757
        // No more events
758
        let none = take_first_event(&mut event_storage);
1✔
759
        assert!(none.is_none());
1✔
760
        assert!(!have_events(&event_storage));
1✔
761
    }
1✔
762
}
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