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

kaidokert / picojson-rs / 16259755145

14 Jul 2025 06:34AM UTC coverage: 90.098% (-3.7%) from 93.785%
16259755145

Pull #56

github

web-flow
Merge 9f3cfb7e4 into 6be34a4b2
Pull Request #56: Another big refactor

454 of 677 new or added lines in 9 files covered. (67.06%)

11 existing lines in 3 files now uncovered.

4786 of 5312 relevant lines covered (90.1%)

723.48 hits per line

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

84.05
/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::ujson::EventToken;
9
use crate::{Event, ParseError};
10

11
/// Escape handling trait for abstracting escape sequence processing between parsers
12
pub trait EscapeHandler {
13
    /// Get the current parser state for escape context checking
14
    fn parser_state(&self) -> &crate::shared::State;
15

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

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

22
    /// Begin escape sequence processing (lifecycle method with default no-op implementation)
23
    /// Called when escape sequence processing begins (e.g., on Begin(EscapeSequence))
24
    fn begin_escape_sequence(&mut self) -> Result<(), crate::ParseError> {
57✔
25
        Ok(())
57✔
26
    }
57✔
27

28
    /// Append a single literal byte (for per-byte accumulation patterns)
29
    /// Default implementation is no-op - suitable for parsers that don't need per-byte processing
30
    fn append_literal_byte(&mut self, _byte: u8) -> Result<(), crate::ParseError> {
×
31
        Ok(())
×
32
    }
×
33

34
    /// Begin unicode escape sequence processing
35
    /// Default implementation is no-op - suitable for parsers that don't need special handling
36
    fn begin_unicode_escape(&mut self) -> Result<(), crate::ParseError> {
35✔
37
        Ok(())
35✔
38
    }
35✔
39
}
40

41
/// Result of processing a tokenizer event
42
#[derive(Debug)]
43
pub enum EventResult<'a, 'b> {
44
    /// Event processing is complete, return this event to the user
45
    Complete(Event<'a, 'b>),
46
    /// Continue processing more tokenizer events
47
    Continue,
48
    /// Extract string content (delegate to parser-specific logic)
49
    ExtractString,
50
    /// Extract key content (delegate to parser-specific logic)
51
    ExtractKey,
52
    /// Extract number content (delegate to parser-specific logic)
53
    /// bool indicates if number was terminated by container delimiter
54
    ExtractNumber(bool),
55
}
56

57
/// Process Begin events that have similar patterns between parsers
58
pub fn process_begin_events<C: ContentExtractor>(
4,409✔
59
    event: &crate::ujson::Event,
4,409✔
60
    content_extractor: &mut C,
4,409✔
61
) -> Option<EventResult<'static, 'static>> {
4,409✔
62
    use crate::shared::{ContentRange, State};
63

64
    match event {
3,515✔
65
        // String/Key Begin events - nearly identical patterns
66
        crate::ujson::Event::Begin(EventToken::Key) => {
67
            let pos = content_extractor.current_position();
360✔
68
            *content_extractor.parser_state_mut() = State::Key(pos);
360✔
69
            content_extractor.begin_string_content(pos);
360✔
70
            Some(EventResult::Continue)
360✔
71
        }
72
        crate::ujson::Event::Begin(EventToken::String) => {
73
            let pos = content_extractor.current_position();
773✔
74
            log::debug!("[NEW] Begin(String): storing pos={} in State::String", pos);
773✔
75
            *content_extractor.parser_state_mut() = State::String(pos);
773✔
76
            content_extractor.begin_string_content(pos);
773✔
77
            Some(EventResult::Continue)
773✔
78
        }
79

80
        // Number Begin events - identical logic
81
        crate::ujson::Event::Begin(
82
            EventToken::Number | EventToken::NumberAndArray | EventToken::NumberAndObject,
83
        ) => {
84
            let pos = content_extractor.current_position();
481✔
85
            let number_start = ContentRange::number_start_from_current(pos);
481✔
86
            *content_extractor.parser_state_mut() = State::Number(number_start);
481✔
87
            Some(EventResult::Continue)
481✔
88
        }
89

90
        // Primitive Begin events - identical logic
91
        crate::ujson::Event::Begin(EventToken::True | EventToken::False | EventToken::Null) => {
92
            Some(EventResult::Continue)
28✔
93
        }
94

95
        _ => None,
2,767✔
96
    }
97
}
4,409✔
98

99
/// Clear event storage array - utility function
100
pub fn clear_events(event_storage: &mut [Option<crate::ujson::Event>; 2]) {
21,116✔
101
    event_storage[0] = None;
21,116✔
102
    event_storage[1] = None;
21,116✔
103
}
21,116✔
104

105
/// Trait for content extraction operations that differ between parsers
106
/// Consolidates ParserContext and ContentExtractor functionality
107
pub trait ContentExtractor: EscapeHandler {
108
    /// Get current position in the input
109
    fn current_position(&self) -> usize;
110

111
    /// Begin string/key content processing at current position
112
    fn begin_string_content(&mut self, pos: usize);
113

114
    /// Get mutable access to parser state
115
    fn parser_state_mut(&mut self) -> &mut crate::shared::State;
116

117
    /// Get mutable access to the Unicode escape collector
118
    /// This eliminates the need for wrapper methods that just forward calls
119
    fn unicode_escape_collector_mut(
120
        &mut self,
121
    ) -> &mut crate::escape_processor::UnicodeEscapeCollector;
122

123
    /// Extract string content using parser-specific logic
124
    fn extract_string_content(
125
        &mut self,
126
        start_pos: usize,
127
    ) -> Result<crate::Event<'_, '_>, crate::ParseError>;
128

129
    /// Extract key content using parser-specific logic
130
    fn extract_key_content(
131
        &mut self,
132
        start_pos: usize,
133
    ) -> Result<crate::Event<'_, '_>, crate::ParseError>;
134

135
    /// Extract number content using parser-specific logic
136
    fn extract_number_content(
137
        &mut self,
138
        start_pos: usize,
139
        from_container_end: bool,
140
    ) -> Result<crate::Event<'_, '_>, crate::ParseError>;
141

142
    /// Shared validation and extraction for string content
143
    fn validate_and_extract_string(&mut self) -> Result<crate::Event<'_, '_>, crate::ParseError> {
436✔
144
        let start_pos = match *self.parser_state() {
436✔
145
            crate::shared::State::String(pos) => pos,
436✔
146
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
147
        };
148

149
        // Check for incomplete surrogate pairs before ending the string
150
        if self
436✔
151
            .unicode_escape_collector_mut()
436✔
152
            .has_pending_high_surrogate()
436✔
153
        {
154
            return Err(crate::ParseError::InvalidUnicodeCodepoint);
3✔
155
        }
433✔
156

157
        *self.parser_state_mut() = crate::shared::State::None;
433✔
158
        self.extract_string_content(start_pos)
433✔
159
    }
436✔
160

161
    /// Shared validation and extraction for key content
162
    fn validate_and_extract_key(&mut self) -> Result<crate::Event<'_, '_>, crate::ParseError> {
329✔
163
        let start_pos = match *self.parser_state() {
329✔
164
            crate::shared::State::Key(pos) => pos,
329✔
165
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
166
        };
167

168
        // Check for incomplete surrogate pairs before ending the key
169
        if self
329✔
170
            .unicode_escape_collector_mut()
329✔
171
            .has_pending_high_surrogate()
329✔
172
        {
173
            return Err(crate::ParseError::InvalidUnicodeCodepoint);
×
174
        }
329✔
175

176
        *self.parser_state_mut() = crate::shared::State::None;
329✔
177
        self.extract_key_content(start_pos)
329✔
178
    }
329✔
179

180
    /// Shared validation and extraction for number content
UNCOV
181
    fn validate_and_extract_number(
×
UNCOV
182
        &mut self,
×
UNCOV
183
        from_container_end: bool,
×
UNCOV
184
    ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
×
UNCOV
185
        let start_pos = match *self.parser_state() {
×
UNCOV
186
            crate::shared::State::Number(pos) => pos,
×
187
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
188
        };
189

UNCOV
190
        *self.parser_state_mut() = crate::shared::State::None;
×
UNCOV
191
        self.extract_number_content(start_pos, from_container_end)
×
UNCOV
192
    }
×
193
}
194

195
/// Creates a standard tokenizer callback for event storage
196
///
197
/// This callback stores tokenizer events in the parser's event array, filling the first
198
/// available slot. This pattern is identical across both SliceParser and StreamParser.
199
pub fn create_tokenizer_callback<'a>(
21,119✔
200
    event_storage: &'a mut [Option<crate::ujson::Event>; 2],
21,119✔
201
) -> impl FnMut(crate::ujson::Event, usize) + 'a {
21,119✔
202
    |event, _len| {
8,636✔
203
        for evt in event_storage.iter_mut() {
9,559✔
204
            if evt.is_none() {
9,559✔
205
                *evt = Some(event);
8,635✔
206
                return;
8,635✔
207
            }
924✔
208
        }
209
    }
8,636✔
210
}
21,119✔
211

212
/// Shared utility to check if any events are waiting to be processed
213
pub fn have_events(event_storage: &[Option<crate::ujson::Event>; 2]) -> bool {
49,148✔
214
    event_storage.iter().any(|evt| evt.is_some())
83,487✔
215
}
49,148✔
216

217
/// Shared utility to extract the first available event from storage
218
pub fn take_first_event(
8,626✔
219
    event_storage: &mut [Option<crate::ujson::Event>; 2],
8,626✔
220
) -> Option<crate::ujson::Event> {
8,626✔
221
    event_storage.iter_mut().find_map(|e| e.take())
9,539✔
222
}
8,626✔
223

224
/// Process Begin(EscapeSequence) events using the enhanced lifecycle interface
225
pub fn process_begin_escape_sequence_event<H: EscapeHandler>(
958✔
226
    handler: &mut H,
958✔
227
) -> Result<(), crate::ParseError> {
958✔
228
    // Only process if we're inside a string or key
229
    log::debug!(
958✔
NEW
230
        "[NEW] process_begin_escape_sequence_event called, state: {:?}",
×
NEW
231
        handler.parser_state()
×
232
    );
233
    match handler.parser_state() {
958✔
234
        crate::shared::State::String(_) | crate::shared::State::Key(_) => {
235
            log::debug!("[NEW] calling begin_escape_sequence()");
958✔
236
            handler.begin_escape_sequence()?;
958✔
237
        }
238
        _ => {
NEW
239
            log::debug!("[NEW] ignoring escape sequence - not in string/key context");
×
240
        } // Ignore if not in string/key context
241
    }
242
    Ok(())
958✔
243
}
958✔
244

245
/// Process simple escape sequence events that have similar patterns between parsers
246
pub fn process_simple_escape_event<C: ContentExtractor>(
694✔
247
    escape_token: &EventToken,
694✔
248
    content_extractor: &mut C,
694✔
249
) -> Result<(), crate::ParseError> {
694✔
250
    // Clear any pending high surrogate state when we encounter a simple escape
251
    // This ensures that interrupted surrogate pairs (like \uD801\n\uDC37) are properly rejected
252
    content_extractor.unicode_escape_collector_mut().reset_all();
694✔
253

254
    // Use unified escape token processing from EscapeProcessor
255
    let unescaped_char =
694✔
256
        crate::escape_processor::EscapeProcessor::process_escape_token(escape_token)?;
694✔
257

258
    // Only process if we're inside a string or key
259
    match content_extractor.parser_state() {
694✔
260
        crate::shared::State::String(_) | crate::shared::State::Key(_) => {
261
            content_extractor.handle_simple_escape_char(unescaped_char)?;
694✔
262
        }
263
        _ => {} // Ignore if not in string/key context
×
264
    }
265

266
    Ok(())
692✔
267
}
694✔
268

269
/// Process Unicode escape begin/end events that have similar patterns between parsers
270
pub fn process_unicode_escape_events<C: ContentExtractor>(
1,808✔
271
    event: &crate::ujson::Event,
1,808✔
272
    content_extractor: &mut C,
1,808✔
273
) -> Result<bool, crate::ParseError> {
1,808✔
274
    match event {
914✔
275
        crate::ujson::Event::Begin(EventToken::UnicodeEscape) => {
276
            log::debug!("NEW event_processor: Begin(UnicodeEscape) event received");
220✔
277
            // Start Unicode escape collection - reset collector for new sequence
278
            // Only handle if we're inside a string or key
279
            match content_extractor.parser_state() {
220✔
280
                crate::shared::State::String(_) | crate::shared::State::Key(_) => {
281
                    log::debug!("NEW event_processor: Resetting unicode escape collector");
220✔
282
                    content_extractor.unicode_escape_collector_mut().reset();
220✔
283
                    content_extractor.begin_unicode_escape()?;
220✔
284
                }
285
                _ => {
NEW
286
                    log::debug!(
×
NEW
287
                        "NEW event_processor: Ignoring unicode escape outside string/key context"
×
288
                    );
289
                } // Ignore if not in string/key context
290
            }
291
            Ok(true) // Event was handled
220✔
292
        }
293
        crate::ujson::Event::End(EventToken::UnicodeEscape) => {
294
            log::debug!("NEW event_processor: End(UnicodeEscape) event received");
202✔
295
            // Handle end of Unicode escape sequence (\uXXXX)
296
            match content_extractor.parser_state() {
202✔
297
                crate::shared::State::String(_) | crate::shared::State::Key(_) => {
298
                    log::debug!("NEW event_processor: Processing unicode escape with collector");
202✔
299
                    content_extractor.process_unicode_escape_with_collector()?;
202✔
300
                }
301
                _ => {
NEW
302
                    log::debug!("NEW event_processor: Ignoring unicode escape end outside string/key context");
×
303
                } // Ignore if not in string/key context
304
            }
305
            Ok(true) // Event was handled
178✔
306
        }
307
        _ => Ok(false), // Event was not handled
1,386✔
308
    }
309
}
1,808✔
310

311
/// Process simple container and primitive events that are identical between parsers
312
pub fn process_simple_events(event: &crate::ujson::Event) -> Option<EventResult<'static, 'static>> {
8,632✔
313
    match event {
1,976✔
314
        // Container events - identical processing
315
        crate::ujson::Event::ObjectStart => Some(EventResult::Complete(Event::StartObject)),
254✔
316
        crate::ujson::Event::ObjectEnd => Some(EventResult::Complete(Event::EndObject)),
209✔
317
        crate::ujson::Event::ArrayStart => Some(EventResult::Complete(Event::StartArray)),
1,673✔
318
        crate::ujson::Event::ArrayEnd => Some(EventResult::Complete(Event::EndArray)),
1,010✔
319

320
        // Primitive values - identical processing
321
        crate::ujson::Event::End(EventToken::True) => {
322
            Some(EventResult::Complete(Event::Bool(true)))
12✔
323
        }
324
        crate::ujson::Event::End(EventToken::False) => {
325
            Some(EventResult::Complete(Event::Bool(false)))
7✔
326
        }
327
        crate::ujson::Event::End(EventToken::Null) => Some(EventResult::Complete(Event::Null)),
8✔
328

329
        // Content extraction triggers - identical logic
330
        crate::ujson::Event::End(EventToken::String) => Some(EventResult::ExtractString),
437✔
331
        crate::ujson::Event::End(EventToken::Key) => Some(EventResult::ExtractKey),
329✔
332
        crate::ujson::Event::End(EventToken::Number) => Some(EventResult::ExtractNumber(false)),
61✔
333
        crate::ujson::Event::End(EventToken::NumberAndArray) => {
334
            Some(EventResult::ExtractNumber(true))
142✔
335
        }
336
        crate::ujson::Event::End(EventToken::NumberAndObject) => {
337
            Some(EventResult::ExtractNumber(true))
86✔
338
        }
339

340
        // All other events need parser-specific handling
341
        _ => None,
4,404✔
342
    }
343
}
8,632✔
344

345
/// Process a specific byte through the tokenizer (for cases where byte is already available)
346
pub fn process_byte_through_tokenizer<T: crate::ujson::BitBucket, C: crate::ujson::DepthCounter>(
20,579✔
347
    byte: u8,
20,579✔
348
    tokenizer: &mut crate::ujson::Tokenizer<T, C>,
20,579✔
349
    event_storage: &mut [Option<crate::ujson::Event>; 2],
20,579✔
350
) -> Result<(), ParseError> {
20,579✔
351
    clear_events(event_storage);
20,579✔
352
    let mut callback = create_tokenizer_callback(event_storage);
20,579✔
353
    tokenizer
20,579✔
354
        .parse_chunk(&[byte], &mut callback)
20,579✔
355
        .map_err(ParseError::TokenizerError)?;
20,579✔
356
    Ok(())
20,574✔
357
}
20,579✔
358

359
/// Finish the tokenizer and collect any final events
360
pub fn finish_tokenizer<T: crate::ujson::BitBucket, C: crate::ujson::DepthCounter>(
537✔
361
    tokenizer: &mut crate::ujson::Tokenizer<T, C>,
537✔
362
    event_storage: &mut [Option<crate::ujson::Event>; 2],
537✔
363
) -> Result<(), ParseError> {
537✔
364
    clear_events(event_storage);
537✔
365
    let mut callback = create_tokenizer_callback(event_storage);
537✔
366
    tokenizer
537✔
367
        .finish(&mut callback)
537✔
368
        .map_err(ParseError::TokenizerError)?;
537✔
369
    Ok(())
533✔
370
}
537✔
371

372
#[cfg(test)]
373
mod tests {
374
    use super::*;
375

376
    #[test]
377
    fn test_container_events() {
1✔
378
        assert!(matches!(
1✔
379
            process_simple_events(&crate::ujson::Event::ObjectStart),
1✔
380
            Some(EventResult::Complete(Event::StartObject))
381
        ));
382

383
        assert!(matches!(
1✔
384
            process_simple_events(&crate::ujson::Event::ArrayEnd),
1✔
385
            Some(EventResult::Complete(Event::EndArray))
386
        ));
387
    }
1✔
388

389
    #[test]
390
    fn test_primitive_events() {
1✔
391
        assert!(matches!(
1✔
392
            process_simple_events(&crate::ujson::Event::End(EventToken::True)),
1✔
393
            Some(EventResult::Complete(Event::Bool(true)))
394
        ));
395

396
        assert!(matches!(
1✔
397
            process_simple_events(&crate::ujson::Event::End(EventToken::Null)),
1✔
398
            Some(EventResult::Complete(Event::Null))
399
        ));
400
    }
1✔
401

402
    #[test]
403
    fn test_extraction_triggers() {
1✔
404
        assert!(matches!(
1✔
405
            process_simple_events(&crate::ujson::Event::End(EventToken::String)),
1✔
406
            Some(EventResult::ExtractString)
407
        ));
408

409
        assert!(matches!(
1✔
410
            process_simple_events(&crate::ujson::Event::End(EventToken::Number)),
1✔
411
            Some(EventResult::ExtractNumber(false))
412
        ));
413

414
        assert!(matches!(
1✔
415
            process_simple_events(&crate::ujson::Event::End(EventToken::NumberAndArray)),
1✔
416
            Some(EventResult::ExtractNumber(true))
417
        ));
418
    }
1✔
419

420
    #[test]
421
    fn test_complex_events_not_handled() {
1✔
422
        assert!(process_simple_events(&crate::ujson::Event::Begin(EventToken::String)).is_none());
1✔
423
        assert!(
1✔
424
            process_simple_events(&crate::ujson::Event::Begin(EventToken::EscapeQuote)).is_none()
1✔
425
        );
426
    }
1✔
427

428
    // Mock ContentExtractor for testing
429
    struct MockContentExtractor {
430
        position: usize,
431
        state: crate::shared::State,
432
        string_begin_calls: Vec<usize>,
433
    }
434

435
    impl MockContentExtractor {
436
        fn new() -> Self {
5✔
437
            Self {
5✔
438
                position: 42,
5✔
439
                state: crate::shared::State::None,
5✔
440
                string_begin_calls: Vec::new(),
5✔
441
            }
5✔
442
        }
5✔
443
    }
444

445
    impl EscapeHandler for MockContentExtractor {
446
        fn parser_state(&self) -> &crate::shared::State {
×
447
            &self.state
×
448
        }
×
449

450
        fn process_unicode_escape_with_collector(&mut self) -> Result<(), crate::ParseError> {
×
451
            Ok(())
×
452
        }
×
453

454
        fn handle_simple_escape_char(&mut self, _escape_char: u8) -> Result<(), crate::ParseError> {
×
455
            Ok(())
×
456
        }
×
457
    }
458

459
    impl ContentExtractor for MockContentExtractor {
460
        fn current_position(&self) -> usize {
3✔
461
            self.position
3✔
462
        }
3✔
463

464
        fn begin_string_content(&mut self, pos: usize) {
2✔
465
            self.string_begin_calls.push(pos);
2✔
466
        }
2✔
467

468
        fn parser_state_mut(&mut self) -> &mut crate::shared::State {
3✔
469
            &mut self.state
3✔
470
        }
3✔
471

472
        fn unicode_escape_collector_mut(
×
473
            &mut self,
×
474
        ) -> &mut crate::escape_processor::UnicodeEscapeCollector {
×
475
            unimplemented!("Mock doesn't need unicode collector")
×
476
        }
477

478
        fn extract_string_content(
×
479
            &mut self,
×
480
            _start_pos: usize,
×
481
        ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
×
482
            unimplemented!("Mock doesn't need extraction")
×
483
        }
484

485
        fn extract_key_content(
×
486
            &mut self,
×
487
            _start_pos: usize,
×
488
        ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
×
489
            unimplemented!("Mock doesn't need extraction")
×
490
        }
491

492
        fn extract_number_content(
×
493
            &mut self,
×
494
            _start_pos: usize,
×
495
            _from_container_end: bool,
×
496
        ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
×
497
            unimplemented!("Mock doesn't need extraction")
×
498
        }
499
    }
500

501
    #[test]
502
    fn test_begin_events_key() {
1✔
503
        let mut context = MockContentExtractor::new();
1✔
504
        let event = crate::ujson::Event::Begin(EventToken::Key);
1✔
505

506
        let result = process_begin_events(&event, &mut context);
1✔
507

508
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
509
        assert!(matches!(context.state, crate::shared::State::Key(42)));
1✔
510
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
511
    }
1✔
512

513
    #[test]
514
    fn test_begin_events_string() {
1✔
515
        let mut context = MockContentExtractor::new();
1✔
516
        let event = crate::ujson::Event::Begin(EventToken::String);
1✔
517

518
        let result = process_begin_events(&event, &mut context);
1✔
519

520
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
521
        assert!(matches!(context.state, crate::shared::State::String(42)));
1✔
522
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
523
    }
1✔
524

525
    #[test]
526
    fn test_begin_events_number() {
1✔
527
        let mut context = MockContentExtractor::new();
1✔
528
        let event = crate::ujson::Event::Begin(EventToken::Number);
1✔
529

530
        let result = process_begin_events(&event, &mut context);
1✔
531

532
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
533
        // Number should get position adjusted by ContentRange::number_start_from_current
534
        assert!(matches!(context.state, crate::shared::State::Number(_)));
1✔
535
        assert_eq!(context.string_begin_calls, Vec::<usize>::new()); // No string calls for numbers
1✔
536
    }
1✔
537

538
    #[test]
539
    fn test_begin_events_primitives() {
1✔
540
        let mut context = MockContentExtractor::new();
1✔
541

542
        for token in [EventToken::True, EventToken::False, EventToken::Null] {
3✔
543
            let event = crate::ujson::Event::Begin(token);
3✔
544
            let result = process_begin_events(&event, &mut context);
3✔
545
            assert!(matches!(result, Some(EventResult::Continue)));
3✔
546
        }
547

548
        // Should not affect state or string processing
549
        assert!(matches!(context.state, crate::shared::State::None));
1✔
550
        assert!(context.string_begin_calls.is_empty());
1✔
551
    }
1✔
552

553
    #[test]
554
    fn test_begin_events_not_handled() {
1✔
555
        let mut context = MockContentExtractor::new();
1✔
556
        let event = crate::ujson::Event::Begin(EventToken::EscapeQuote);
1✔
557

558
        let result = process_begin_events(&event, &mut context);
1✔
559

560
        assert!(result.is_none());
1✔
561
        assert!(matches!(context.state, crate::shared::State::None));
1✔
562
        assert!(context.string_begin_calls.is_empty());
1✔
563
    }
1✔
564

565
    #[test]
566
    fn test_tokenizer_callback() {
1✔
567
        let mut event_storage = [None, None];
1✔
568

569
        // Initially no events
570
        assert!(!have_events(&event_storage));
1✔
571

572
        {
1✔
573
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
574

1✔
575
            // Add first event
1✔
576
            callback(crate::ujson::Event::ObjectStart, 1);
1✔
577
        }
1✔
578
        assert!(have_events(&event_storage));
1✔
579
        assert!(event_storage[0].is_some());
1✔
580
        assert!(event_storage[1].is_none());
1✔
581

582
        {
1✔
583
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
584
            // Add second event
1✔
585
            callback(crate::ujson::Event::ArrayStart, 1);
1✔
586
        }
1✔
587
        assert!(event_storage[0].is_some());
1✔
588
        assert!(event_storage[1].is_some());
1✔
589

590
        {
1✔
591
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
592
            // Storage is full, third event should be ignored (no panic)
1✔
593
            callback(crate::ujson::Event::ObjectEnd, 1);
1✔
594
        }
1✔
595
        assert!(event_storage[0].is_some());
1✔
596
        assert!(event_storage[1].is_some());
1✔
597
    }
1✔
598

599
    #[test]
600
    fn test_event_extraction() {
1✔
601
        let mut event_storage = [
1✔
602
            Some(crate::ujson::Event::ObjectStart),
1✔
603
            Some(crate::ujson::Event::ArrayStart),
1✔
604
        ];
1✔
605

606
        // Extract first event
607
        let first = take_first_event(&mut event_storage);
1✔
608
        assert!(matches!(first, Some(crate::ujson::Event::ObjectStart)));
1✔
609
        assert!(event_storage[0].is_none());
1✔
610
        assert!(event_storage[1].is_some());
1✔
611

612
        // Extract second event
613
        let second = take_first_event(&mut event_storage);
1✔
614
        assert!(matches!(second, Some(crate::ujson::Event::ArrayStart)));
1✔
615
        assert!(event_storage[0].is_none());
1✔
616
        assert!(event_storage[1].is_none());
1✔
617

618
        // No more events
619
        let none = take_first_event(&mut event_storage);
1✔
620
        assert!(none.is_none());
1✔
621
        assert!(!have_events(&event_storage));
1✔
622
    }
1✔
623
}
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