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

kaidokert / picojson-rs / 16314047069

16 Jul 2025 08:09AM UTC coverage: 94.393% (+0.5%) from 93.864%
16314047069

push

github

web-flow
Big old refactor (#62)

* Big old refactor

556 of 606 new or added lines in 9 files covered. (91.75%)

4 existing lines in 2 files now uncovered.

4579 of 4851 relevant lines covered (94.39%)

646.87 hits per line

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

85.71
/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
        mut 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
        loop {
51
            while !have_events(&self.parser_state.evts) {
30,024✔
52
                if let Some(byte) = provider.next_byte()? {
21,478✔
53
                    {
54
                        clear_events(&mut self.parser_state.evts);
20,427✔
55
                        let mut callback = create_tokenizer_callback(&mut self.parser_state.evts);
20,427✔
56
                        self.tokenizer
20,427✔
57
                            .parse_chunk(&[byte], &mut callback)
20,427✔
58
                            .map_err(ParseError::TokenizerError)?;
20,427✔
59
                    }
60

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

210
    /// Extract a completed number using shared number parsing logic
211
    ///
212
    /// # Arguments
213
    /// * `start_pos` - Position where the number started
214
    /// * `from_container_end` - True if number was terminated by container delimiter
215
    /// * `finished` - True if the parser has finished processing input (StreamParser-specific)
216
    fn extract_number(
217
        &mut self,
218
        start_pos: usize,
219
        from_container_end: bool,
220
        finished: bool,
221
    ) -> Result<Event<'_, '_>, ParseError>;
222

223
    /// Check if the underlying source is finished (e.g., EOF for a stream).
224
    /// The default is `true`, suitable for complete sources like slices.
NEW
225
    fn is_finished(&self) -> bool {
×
NEW
226
        true
×
NEW
227
    }
×
228

229
    /// Shared validation and extraction for string content
230
    fn validate_and_extract_string(&mut self) -> Result<Event<'_, '_>, ParseError> {
431✔
231
        let start_pos = match *self.parser_state() {
431✔
232
            State::String(pos) => pos,
431✔
233
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
234
        };
235

236
        // Check for incomplete surrogate pairs before ending the string
237
        if self
431✔
238
            .unicode_escape_collector_mut()
431✔
239
            .has_pending_high_surrogate()
431✔
240
        {
241
            return Err(ParseError::InvalidUnicodeCodepoint);
3✔
242
        }
428✔
243

244
        *self.parser_state_mut() = State::None;
428✔
245
        self.extract_string_content(start_pos)
428✔
246
    }
431✔
247

248
    /// Shared validation and extraction for key content
249
    fn validate_and_extract_key(&mut self) -> Result<Event<'_, '_>, ParseError> {
325✔
250
        let start_pos = match *self.parser_state() {
325✔
251
            State::Key(pos) => pos,
325✔
252
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
253
        };
254

255
        // Check for incomplete surrogate pairs before ending the key
256
        if self
325✔
257
            .unicode_escape_collector_mut()
325✔
258
            .has_pending_high_surrogate()
325✔
259
        {
NEW
260
            return Err(ParseError::InvalidUnicodeCodepoint);
×
261
        }
325✔
262

263
        *self.parser_state_mut() = State::None;
325✔
264
        self.extract_key_content(start_pos)
325✔
265
    }
325✔
266

267
    /// Shared validation and extraction for number content
268
    fn validate_and_extract_number(
27✔
269
        &mut self,
27✔
270
        from_container_end: bool,
27✔
271
    ) -> Result<Event<'_, '_>, ParseError> {
27✔
272
        let start_pos = match *self.parser_state() {
27✔
273
            State::Number(pos) => pos,
27✔
274
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
275
        };
276

277
        *self.parser_state_mut() = State::None;
27✔
278
        self.extract_number(start_pos, from_container_end, true)
27✔
279
    }
27✔
280

281
    /// Get the current parser state for escape context checking
282
    fn parser_state(&self) -> &State;
283

284
    /// Process Unicode escape sequence using shared collector logic
285
    fn process_unicode_escape_with_collector(&mut self) -> Result<(), ParseError>;
286

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

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

294
    /// Begin unicode escape sequence processing
295
    fn begin_unicode_escape(&mut self) -> Result<(), ParseError>;
296

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

317
            // Number Begin events - identical logic
318
            ujson::Event::Begin(
319
                EventToken::Number | EventToken::NumberAndArray | EventToken::NumberAndObject,
320
            ) => {
321
                let pos = self.current_position();
471✔
322
                let number_start = ContentRange::number_start_from_current(pos);
471✔
323
                *self.parser_state_mut() = State::Number(number_start);
471✔
324
                Some(EventResult::Continue)
471✔
325
            }
326

327
            // Primitive Begin events - identical logic
328
            ujson::Event::Begin(EventToken::True | EventToken::False | EventToken::Null) => {
329
                Some(EventResult::Continue)
25✔
330
            }
331

332
            _ => None,
2,746✔
333
        }
334
    }
4,366✔
335

336
    /// Process Begin(EscapeSequence) events using the enhanced lifecycle interface
337
    fn process_begin_escape_sequence_event(&mut self) -> Result<(), ParseError> {
951✔
338
        // Only process if we're inside a string or key
339
        match self.parser_state() {
951✔
340
            State::String(_) | State::Key(_) => {
341
                self.begin_escape_sequence()?;
951✔
342
            }
NEW
343
            _ => {} // Ignore if not in string/key context
×
344
        }
345
        Ok(())
951✔
346
    }
951✔
347

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

354
        // Use unified escape token processing from EscapeProcessor
355
        let unescaped_char = EscapeProcessor::process_escape_token(escape_token)?;
688✔
356

357
        // Only process if we're inside a string or key
358
        match self.parser_state() {
688✔
359
            State::String(_) | State::Key(_) => {
360
                self.handle_simple_escape_char(unescaped_char)?;
688✔
361
            }
NEW
362
            _ => {} // Ignore if not in string/key context
×
363
        }
364

365
        Ok(())
686✔
366
    }
688✔
367

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

398
/// Clear event storage array - utility function
399
pub fn clear_events(event_storage: &mut [Option<ujson::Event>; 2]) {
20,949✔
400
    event_storage[0] = None;
20,949✔
401
    event_storage[1] = None;
20,949✔
402
}
20,949✔
403

404
/// Creates a standard tokenizer callback for event storage
405
///
406
/// This callback stores tokenizer events in the parser's event array, filling the first
407
/// available slot. This pattern is identical across both SliceParser and StreamParser.
408
pub fn create_tokenizer_callback(
20,952✔
409
    event_storage: &mut [Option<ujson::Event>; 2],
20,952✔
410
) -> impl FnMut(ujson::Event, usize) + '_ {
20,952✔
411
    |event, _len| {
8,559✔
412
        for evt in event_storage.iter_mut() {
9,471✔
413
            if evt.is_none() {
9,471✔
414
                *evt = Some(event);
8,558✔
415
                return;
8,558✔
416
            }
913✔
417
        }
418
    }
8,559✔
419
}
20,952✔
420

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

426
/// Shared utility to extract the first available event from storage
427
pub fn take_first_event(event_storage: &mut [Option<ujson::Event>; 2]) -> Option<ujson::Event> {
8,549✔
428
    event_storage.iter_mut().find_map(|e| e.take())
9,451✔
429
}
8,549✔
430

431
/// Process simple container and primitive events that are identical between parsers
432
pub fn process_simple_events(event: &ujson::Event) -> Option<EventResult<'static, 'static>> {
8,555✔
433
    match event {
1,947✔
434
        // Container events - identical processing
435
        ujson::Event::ObjectStart => Some(EventResult::Complete(Event::StartObject)),
251✔
436
        ujson::Event::ObjectEnd => Some(EventResult::Complete(Event::EndObject)),
206✔
437
        ujson::Event::ArrayStart => Some(EventResult::Complete(Event::StartArray)),
1,670✔
438
        ujson::Event::ArrayEnd => Some(EventResult::Complete(Event::EndArray)),
1,007✔
439

440
        // Primitive values - identical processing
441
        ujson::Event::End(EventToken::True) => Some(EventResult::Complete(Event::Bool(true))),
11✔
442
        ujson::Event::End(EventToken::False) => Some(EventResult::Complete(Event::Bool(false))),
6✔
443
        ujson::Event::End(EventToken::Null) => Some(EventResult::Complete(Event::Null)),
7✔
444

445
        // Content extraction triggers - identical logic
446
        ujson::Event::End(EventToken::String) => Some(EventResult::ExtractString),
432✔
447
        ujson::Event::End(EventToken::Key) => Some(EventResult::ExtractKey),
325✔
448
        ujson::Event::End(EventToken::Number) => Some(EventResult::ExtractNumber(false)),
56✔
449
        ujson::Event::End(EventToken::NumberAndArray) => Some(EventResult::ExtractNumber(true)),
140✔
450
        ujson::Event::End(EventToken::NumberAndObject) => Some(EventResult::ExtractNumber(true)),
83✔
451

452
        // All other events need parser-specific handling
453
        _ => None,
4,361✔
454
    }
455
}
8,555✔
456

457
#[cfg(test)]
458
mod tests {
459
    use super::*;
460

461
    #[test]
462
    fn test_container_events() {
1✔
463
        assert!(matches!(
1✔
464
            process_simple_events(&ujson::Event::ObjectStart),
1✔
465
            Some(EventResult::Complete(Event::StartObject))
466
        ));
467

468
        assert!(matches!(
1✔
469
            process_simple_events(&ujson::Event::ArrayEnd),
1✔
470
            Some(EventResult::Complete(Event::EndArray))
471
        ));
472
    }
1✔
473

474
    #[test]
475
    fn test_primitive_events() {
1✔
476
        assert!(matches!(
1✔
477
            process_simple_events(&ujson::Event::End(EventToken::True)),
1✔
478
            Some(EventResult::Complete(Event::Bool(true)))
479
        ));
480

481
        assert!(matches!(
1✔
482
            process_simple_events(&ujson::Event::End(EventToken::Null)),
1✔
483
            Some(EventResult::Complete(Event::Null))
484
        ));
485
    }
1✔
486

487
    #[test]
488
    fn test_extraction_triggers() {
1✔
489
        assert!(matches!(
1✔
490
            process_simple_events(&ujson::Event::End(EventToken::String)),
1✔
491
            Some(EventResult::ExtractString)
492
        ));
493

494
        assert!(matches!(
1✔
495
            process_simple_events(&ujson::Event::End(EventToken::Number)),
1✔
496
            Some(EventResult::ExtractNumber(false))
497
        ));
498

499
        assert!(matches!(
1✔
500
            process_simple_events(&ujson::Event::End(EventToken::NumberAndArray)),
1✔
501
            Some(EventResult::ExtractNumber(true))
502
        ));
503
    }
1✔
504

505
    #[test]
506
    fn test_complex_events_not_handled() {
1✔
507
        assert!(process_simple_events(&ujson::Event::Begin(EventToken::String)).is_none());
1✔
508
        assert!(process_simple_events(&ujson::Event::Begin(EventToken::EscapeQuote)).is_none());
1✔
509
    }
1✔
510

511
    // Mock ContentExtractor for testing
512
    struct MockContentExtractor {
513
        position: usize,
514
        state: State,
515
        string_begin_calls: Vec<usize>,
516
    }
517

518
    impl MockContentExtractor {
519
        fn new() -> Self {
5✔
520
            Self {
5✔
521
                position: 42,
5✔
522
                state: State::None,
5✔
523
                string_begin_calls: Vec::new(),
5✔
524
            }
5✔
525
        }
5✔
526
    }
527

528
    impl ContentExtractor for MockContentExtractor {
NEW
529
        fn next_byte(&mut self) -> Result<Option<u8>, ParseError> {
×
NEW
530
            Ok(None)
×
UNCOV
531
        }
×
532

533
        fn current_position(&self) -> usize {
3✔
534
            self.position
3✔
535
        }
3✔
536

537
        fn begin_string_content(&mut self, pos: usize) {
2✔
538
            self.string_begin_calls.push(pos);
2✔
539
        }
2✔
540

541
        fn parser_state_mut(&mut self) -> &mut State {
3✔
542
            &mut self.state
3✔
543
        }
3✔
544

NEW
545
        fn unicode_escape_collector_mut(&mut self) -> &mut UnicodeEscapeCollector {
×
546
            unimplemented!("Mock doesn't need unicode collector")
×
547
        }
548

549
        fn extract_string_content(
×
550
            &mut self,
×
551
            _start_pos: usize,
×
NEW
552
        ) -> Result<Event<'_, '_>, ParseError> {
×
553
            unimplemented!("Mock doesn't need extraction")
×
554
        }
555

NEW
556
        fn extract_key_content(&mut self, _start_pos: usize) -> Result<Event<'_, '_>, ParseError> {
×
557
            unimplemented!("Mock doesn't need extraction")
×
558
        }
559

NEW
560
        fn extract_number(
×
561
            &mut self,
×
562
            _start_pos: usize,
×
563
            _from_container_end: bool,
×
NEW
564
            _finished: bool,
×
NEW
565
        ) -> Result<Event<'_, '_>, ParseError> {
×
UNCOV
566
            unimplemented!("Mock doesn't need extraction")
×
567
        }
568

NEW
569
        fn parser_state(&self) -> &State {
×
NEW
570
            &self.state
×
NEW
571
        }
×
572

NEW
573
        fn process_unicode_escape_with_collector(&mut self) -> Result<(), ParseError> {
×
NEW
574
            Ok(())
×
NEW
575
        }
×
576

NEW
577
        fn handle_simple_escape_char(&mut self, _escape_char: u8) -> Result<(), ParseError> {
×
NEW
578
            Ok(())
×
NEW
579
        }
×
580

NEW
581
        fn begin_unicode_escape(&mut self) -> Result<(), ParseError> {
×
NEW
582
            Ok(())
×
NEW
583
        }
×
584

NEW
585
        fn begin_escape_sequence(&mut self) -> Result<(), ParseError> {
×
NEW
586
            Ok(())
×
NEW
587
        }
×
588
    }
589

590
    #[test]
591
    fn test_begin_events_key() {
1✔
592
        let mut context = MockContentExtractor::new();
1✔
593
        let event = ujson::Event::Begin(EventToken::Key);
1✔
594

595
        let result = context.process_begin_events(&event);
1✔
596

597
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
598
        assert!(matches!(context.state, State::Key(42)));
1✔
599
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
600
    }
1✔
601

602
    #[test]
603
    fn test_begin_events_string() {
1✔
604
        let mut context = MockContentExtractor::new();
1✔
605
        let event = ujson::Event::Begin(EventToken::String);
1✔
606

607
        let result = context.process_begin_events(&event);
1✔
608

609
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
610
        assert!(matches!(context.state, State::String(42)));
1✔
611
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
612
    }
1✔
613

614
    #[test]
615
    fn test_begin_events_number() {
1✔
616
        let mut context = MockContentExtractor::new();
1✔
617
        let event = ujson::Event::Begin(EventToken::Number);
1✔
618

619
        let result = context.process_begin_events(&event);
1✔
620

621
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
622
        // Number should get position adjusted by ContentRange::number_start_from_current
623
        assert!(matches!(context.state, State::Number(_)));
1✔
624
        assert_eq!(context.string_begin_calls, Vec::<usize>::new()); // No string calls for numbers
1✔
625
    }
1✔
626

627
    #[test]
628
    fn test_begin_events_primitives() {
1✔
629
        let mut context = MockContentExtractor::new();
1✔
630

631
        for token in [EventToken::True, EventToken::False, EventToken::Null] {
3✔
632
            let event = ujson::Event::Begin(token);
3✔
633
            let result = context.process_begin_events(&event);
3✔
634
            assert!(matches!(result, Some(EventResult::Continue)));
3✔
635
        }
636

637
        // Should not affect state or string processing
638
        assert!(matches!(context.state, State::None));
1✔
639
        assert!(context.string_begin_calls.is_empty());
1✔
640
    }
1✔
641

642
    #[test]
643
    fn test_begin_events_not_handled() {
1✔
644
        let mut context = MockContentExtractor::new();
1✔
645
        let event = ujson::Event::Begin(EventToken::EscapeQuote);
1✔
646

647
        let result = context.process_begin_events(&event);
1✔
648

649
        assert!(result.is_none());
1✔
650
        assert!(matches!(context.state, State::None));
1✔
651
        assert!(context.string_begin_calls.is_empty());
1✔
652
    }
1✔
653

654
    #[test]
655
    fn test_tokenizer_callback() {
1✔
656
        let mut event_storage = [None, None];
1✔
657

658
        // Initially no events
659
        assert!(!have_events(&event_storage));
1✔
660

661
        {
1✔
662
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
663

1✔
664
            // Add first event
1✔
665
            callback(ujson::Event::ObjectStart, 1);
1✔
666
        }
1✔
667
        assert!(have_events(&event_storage));
1✔
668
        assert!(event_storage[0].is_some());
1✔
669
        assert!(event_storage[1].is_none());
1✔
670

671
        {
1✔
672
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
673
            // Add second event
1✔
674
            callback(ujson::Event::ArrayStart, 1);
1✔
675
        }
1✔
676
        assert!(event_storage[0].is_some());
1✔
677
        assert!(event_storage[1].is_some());
1✔
678

679
        {
1✔
680
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
681
            // Storage is full, third event should be ignored (no panic)
1✔
682
            callback(ujson::Event::ObjectEnd, 1);
1✔
683
        }
1✔
684
        assert!(event_storage[0].is_some());
1✔
685
        assert!(event_storage[1].is_some());
1✔
686
    }
1✔
687

688
    #[test]
689
    fn test_event_extraction() {
1✔
690
        let mut event_storage = [
1✔
691
            Some(ujson::Event::ObjectStart),
1✔
692
            Some(ujson::Event::ArrayStart),
1✔
693
        ];
1✔
694

695
        // Extract first event
696
        let first = take_first_event(&mut event_storage);
1✔
697
        assert!(matches!(first, Some(ujson::Event::ObjectStart)));
1✔
698
        assert!(event_storage[0].is_none());
1✔
699
        assert!(event_storage[1].is_some());
1✔
700

701
        // Extract second event
702
        let second = take_first_event(&mut event_storage);
1✔
703
        assert!(matches!(second, Some(ujson::Event::ArrayStart)));
1✔
704
        assert!(event_storage[0].is_none());
1✔
705
        assert!(event_storage[1].is_none());
1✔
706

707
        // No more events
708
        let none = take_first_event(&mut event_storage);
1✔
709
        assert!(none.is_none());
1✔
710
        assert!(!have_events(&event_storage));
1✔
711
    }
1✔
712
}
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