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

kaidokert / picojson-rs / 16311110679

16 Jul 2025 05:21AM UTC coverage: 94.251% (+0.4%) from 93.864%
16311110679

Pull #60

github

web-flow
Merge 2e5b24438 into d7962e604
Pull Request #60: Clean refactor

526 of 577 new or added lines in 9 files covered. (91.16%)

3 existing lines in 2 files now uncovered.

4640 of 4923 relevant lines covered (94.25%)

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

12
// --- Content from former parser_core.rs ---
13

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

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

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

57
                    // Call byte accumulator if no events were generated (StreamParser-specific)
58
                    if !have_events(&self.parser_state.evts) {
20,422✔
59
                        byte_accumulator(provider, byte)?;
12,780✔
60
                    }
7,642✔
61
                } else {
62
                    // Handle end of stream - let the provider handle any cleanup
63
                    // For StreamParser, this is where finished flag gets set
64
                    finish_tokenizer(&mut self.tokenizer, &mut self.parser_state.evts)?;
522✔
65

66
                    if !have_events(&self.parser_state.evts) {
518✔
67
                        return Ok(Event::EndDocument);
514✔
68
                    }
4✔
69
                }
70
            }
71

72
            let taken_event = take_first_event(&mut self.parser_state.evts);
8,546✔
73
            let Some(taken) = taken_event else {
8,546✔
NEW
74
                return Err(UnexpectedState::StateMismatch.into());
×
75
            };
76

77
            // Try shared event processors first
78
            if let Some(result) =
5,801✔
79
                process_simple_events(&taken).or_else(|| provider.process_begin_events(&taken))
8,546✔
80
            {
81
                match result {
5,801✔
82
                    EventResult::Complete(event) => return Ok(event),
3,154✔
83
                    EventResult::ExtractString => return provider.validate_and_extract_string(),
431✔
84
                    EventResult::ExtractKey => return provider.validate_and_extract_key(),
325✔
85
                    EventResult::ExtractNumber(from_container_end) => {
277✔
86
                        return provider.validate_and_extract_number(from_container_end)
277✔
87
                    }
88
                    EventResult::Continue => continue,
1,614✔
89
                }
90
            }
2,745✔
91

92
            // Handle parser-specific events based on escape timing
93
            match taken {
137✔
94
                ujson::Event::Begin(EventToken::EscapeSequence) => {
95
                    provider.process_begin_escape_sequence_event()?;
951✔
96
                }
97
                _ if provider.process_unicode_escape_events(&taken)? => {
1,794✔
98
                    // Unicode escape events handled by shared function
396✔
99
                }
396✔
100
                ujson::Event::Begin(
101
                    escape_token @ (EventToken::EscapeQuote
1✔
102
                    | EventToken::EscapeBackslash
103
                    | EventToken::EscapeSlash
104
                    | EventToken::EscapeBackspace
105
                    | EventToken::EscapeFormFeed
106
                    | EventToken::EscapeNewline
107
                    | EventToken::EscapeCarriageReturn
108
                    | EventToken::EscapeTab),
109
                ) if escape_timing == EscapeTiming::OnBegin => {
137✔
110
                    // SliceParser-specific: Handle simple escape sequences on Begin events
111
                    // because CopyOnEscape requires starting unescaping immediately when
112
                    // the escape token begins to maintain zero-copy optimization
113
                    provider.process_simple_escape_event(&escape_token)?;
20✔
114
                }
115
                ujson::Event::End(
116
                    escape_token @ (EventToken::EscapeQuote
136✔
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::OnEnd => {
137✔
125
                    // StreamParser-specific: Handle simple escape sequences on End events
126
                    // because StreamBuffer must wait until the token ends to accumulate
127
                    // all bytes before processing the complete escape sequence
128
                    provider.process_simple_escape_event(&escape_token)?;
668✔
129
                }
130
                _ => {
686✔
131
                    // All other events continue to next iteration
686✔
132
                }
686✔
133
            }
134
        }
135
    }
5,265✔
136
}
137

138
impl<T: ujson::BitBucket, C: ujson::DepthCounter> Default for ParserCore<T, C> {
NEW
139
    fn default() -> Self {
×
NEW
140
        Self::new()
×
UNCOV
141
    }
×
142
}
143

144
/// Enum to specify when escape sequences should be processed
145
#[derive(Debug, Clone, Copy, PartialEq)]
146
pub enum EscapeTiming {
147
    /// Process simple escape sequences on Begin events (SliceParser)
148
    OnBegin,
149
    /// Process simple escape sequences on End events (StreamParser)
150
    OnEnd,
151
}
152

153
// --- Original event_processor.rs content ---
154

155
/// Result of processing a tokenizer event
156
#[derive(Debug)]
157
pub enum EventResult<'a, 'b> {
158
    /// Event processing is complete, return this event to the user
159
    Complete(Event<'a, 'b>),
160
    /// Continue processing more tokenizer events
161
    Continue,
162
    /// Extract string content (delegate to parser-specific logic)
163
    ExtractString,
164
    /// Extract key content (delegate to parser-specific logic)
165
    ExtractKey,
166
    /// Extract number content (delegate to parser-specific logic)
167
    /// bool indicates if number was terminated by container delimiter
168
    ExtractNumber(bool),
169
}
170

171
/// Trait for content extraction operations that differ between parsers
172
/// Consolidates ParserContext and ContentExtractor functionality
173
pub trait ContentExtractor {
174
    /// Get the next byte from the input source
175
    /// Returns None when end of input is reached
176
    fn next_byte(&mut self) -> Result<Option<u8>, ParseError>;
177

178
    /// Get current position in the input
179
    fn current_position(&self) -> usize;
180

181
    /// Begin string/key content processing at current position
182
    fn begin_string_content(&mut self, pos: usize);
183

184
    /// Get mutable access to parser state
185
    fn parser_state_mut(&mut self) -> &mut crate::shared::State;
186

187
    /// Get mutable access to the Unicode escape collector
188
    /// This eliminates the need for wrapper methods that just forward calls
189
    fn unicode_escape_collector_mut(
190
        &mut self,
191
    ) -> &mut crate::escape_processor::UnicodeEscapeCollector;
192

193
    /// Extract string content using parser-specific logic
194
    fn extract_string_content(
195
        &mut self,
196
        start_pos: usize,
197
    ) -> Result<crate::Event<'_, '_>, crate::ParseError>;
198

199
    /// Extract key content using parser-specific logic
200
    fn extract_key_content(
201
        &mut self,
202
        start_pos: usize,
203
    ) -> Result<crate::Event<'_, '_>, crate::ParseError>;
204

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

218
    /// Check if the underlying source is finished (e.g., EOF for a stream).
219
    /// The default is `true`, suitable for complete sources like slices.
NEW
220
    fn is_finished(&self) -> bool {
×
NEW
221
        true
×
NEW
222
    }
×
223

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

231
        // Check for incomplete surrogate pairs before ending the string
232
        if self
431✔
233
            .unicode_escape_collector_mut()
431✔
234
            .has_pending_high_surrogate()
431✔
235
        {
236
            return Err(crate::ParseError::InvalidUnicodeCodepoint);
3✔
237
        }
428✔
238

239
        *self.parser_state_mut() = crate::shared::State::None;
428✔
240
        self.extract_string_content(start_pos)
428✔
241
    }
431✔
242

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

250
        // Check for incomplete surrogate pairs before ending the key
251
        if self
325✔
252
            .unicode_escape_collector_mut()
325✔
253
            .has_pending_high_surrogate()
325✔
254
        {
255
            return Err(crate::ParseError::InvalidUnicodeCodepoint);
×
256
        }
325✔
257

258
        *self.parser_state_mut() = crate::shared::State::None;
325✔
259
        self.extract_key_content(start_pos)
325✔
260
    }
325✔
261

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

272
        *self.parser_state_mut() = crate::shared::State::None;
27✔
273
        self.extract_number(start_pos, from_container_end, true)
27✔
274
    }
27✔
275

276
    // --- Methods from former EscapeHandler trait ---
277

278
    /// Get the current parser state for escape context checking
279
    fn parser_state(&self) -> &crate::shared::State;
280

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

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

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

291
    /// Begin unicode escape sequence processing
292
    fn begin_unicode_escape(&mut self) -> Result<(), crate::ParseError>;
293

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

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

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

329
            _ => None,
2,746✔
330
        }
331
    }
4,366✔
332

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

345
    /// Process simple escape sequence events that have similar patterns between parsers
346
    fn process_simple_escape_event(
688✔
347
        &mut self,
688✔
348
        escape_token: &EventToken,
688✔
349
    ) -> Result<(), crate::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 =
688✔
356
            crate::escape_processor::EscapeProcessor::process_escape_token(escape_token)?;
688✔
357

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

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

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

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

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

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

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

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

446
        // Primitive values - identical processing
447
        crate::ujson::Event::End(EventToken::True) => {
448
            Some(EventResult::Complete(Event::Bool(true)))
11✔
449
        }
450
        crate::ujson::Event::End(EventToken::False) => {
451
            Some(EventResult::Complete(Event::Bool(false)))
6✔
452
        }
453
        crate::ujson::Event::End(EventToken::Null) => Some(EventResult::Complete(Event::Null)),
7✔
454

455
        // Content extraction triggers - identical logic
456
        crate::ujson::Event::End(EventToken::String) => Some(EventResult::ExtractString),
432✔
457
        crate::ujson::Event::End(EventToken::Key) => Some(EventResult::ExtractKey),
325✔
458
        crate::ujson::Event::End(EventToken::Number) => Some(EventResult::ExtractNumber(false)),
56✔
459
        crate::ujson::Event::End(EventToken::NumberAndArray) => {
460
            Some(EventResult::ExtractNumber(true))
140✔
461
        }
462
        crate::ujson::Event::End(EventToken::NumberAndObject) => {
463
            Some(EventResult::ExtractNumber(true))
83✔
464
        }
465

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

471
/// Process a specific byte through the tokenizer (for cases where byte is already available)
472
pub fn process_byte_through_tokenizer<T: crate::ujson::BitBucket, C: crate::ujson::DepthCounter>(
20,427✔
473
    byte: u8,
20,427✔
474
    tokenizer: &mut crate::ujson::Tokenizer<T, C>,
20,427✔
475
    event_storage: &mut [Option<crate::ujson::Event>; 2],
20,427✔
476
) -> Result<(), ParseError> {
20,427✔
477
    clear_events(event_storage);
20,427✔
478
    let mut callback = create_tokenizer_callback(event_storage);
20,427✔
479
    tokenizer
20,427✔
480
        .parse_chunk(&[byte], &mut callback)
20,427✔
481
        .map_err(ParseError::TokenizerError)?;
20,427✔
482
    Ok(())
20,422✔
483
}
20,427✔
484

485
/// Finish the tokenizer and collect any final events
486
pub fn finish_tokenizer<T: crate::ujson::BitBucket, C: crate::ujson::DepthCounter>(
522✔
487
    tokenizer: &mut crate::ujson::Tokenizer<T, C>,
522✔
488
    event_storage: &mut [Option<crate::ujson::Event>; 2],
522✔
489
) -> Result<(), ParseError> {
522✔
490
    clear_events(event_storage);
522✔
491
    let mut callback = create_tokenizer_callback(event_storage);
522✔
492
    tokenizer
522✔
493
        .finish(&mut callback)
522✔
494
        .map_err(ParseError::TokenizerError)?;
522✔
495
    Ok(())
518✔
496
}
522✔
497

498
#[cfg(test)]
499
mod tests {
500
    use super::*;
501

502
    #[test]
503
    fn test_container_events() {
1✔
504
        assert!(matches!(
1✔
505
            process_simple_events(&crate::ujson::Event::ObjectStart),
1✔
506
            Some(EventResult::Complete(Event::StartObject))
507
        ));
508

509
        assert!(matches!(
1✔
510
            process_simple_events(&crate::ujson::Event::ArrayEnd),
1✔
511
            Some(EventResult::Complete(Event::EndArray))
512
        ));
513
    }
1✔
514

515
    #[test]
516
    fn test_primitive_events() {
1✔
517
        assert!(matches!(
1✔
518
            process_simple_events(&crate::ujson::Event::End(EventToken::True)),
1✔
519
            Some(EventResult::Complete(Event::Bool(true)))
520
        ));
521

522
        assert!(matches!(
1✔
523
            process_simple_events(&crate::ujson::Event::End(EventToken::Null)),
1✔
524
            Some(EventResult::Complete(Event::Null))
525
        ));
526
    }
1✔
527

528
    #[test]
529
    fn test_extraction_triggers() {
1✔
530
        assert!(matches!(
1✔
531
            process_simple_events(&crate::ujson::Event::End(EventToken::String)),
1✔
532
            Some(EventResult::ExtractString)
533
        ));
534

535
        assert!(matches!(
1✔
536
            process_simple_events(&crate::ujson::Event::End(EventToken::Number)),
1✔
537
            Some(EventResult::ExtractNumber(false))
538
        ));
539

540
        assert!(matches!(
1✔
541
            process_simple_events(&crate::ujson::Event::End(EventToken::NumberAndArray)),
1✔
542
            Some(EventResult::ExtractNumber(true))
543
        ));
544
    }
1✔
545

546
    #[test]
547
    fn test_complex_events_not_handled() {
1✔
548
        assert!(process_simple_events(&crate::ujson::Event::Begin(EventToken::String)).is_none());
1✔
549
        assert!(
1✔
550
            process_simple_events(&crate::ujson::Event::Begin(EventToken::EscapeQuote)).is_none()
1✔
551
        );
552
    }
1✔
553

554
    // Mock ContentExtractor for testing
555
    struct MockContentExtractor {
556
        position: usize,
557
        state: crate::shared::State,
558
        string_begin_calls: Vec<usize>,
559
    }
560

561
    impl MockContentExtractor {
562
        fn new() -> Self {
5✔
563
            Self {
5✔
564
                position: 42,
5✔
565
                state: crate::shared::State::None,
5✔
566
                string_begin_calls: Vec::new(),
5✔
567
            }
5✔
568
        }
5✔
569
    }
570

571
    impl ContentExtractor for MockContentExtractor {
NEW
572
        fn next_byte(&mut self) -> Result<Option<u8>, crate::ParseError> {
×
NEW
573
            Ok(None)
×
UNCOV
574
        }
×
575

576
        fn current_position(&self) -> usize {
3✔
577
            self.position
3✔
578
        }
3✔
579

580
        fn begin_string_content(&mut self, pos: usize) {
2✔
581
            self.string_begin_calls.push(pos);
2✔
582
        }
2✔
583

584
        fn parser_state_mut(&mut self) -> &mut crate::shared::State {
3✔
585
            &mut self.state
3✔
586
        }
3✔
587

588
        fn unicode_escape_collector_mut(
×
589
            &mut self,
×
590
        ) -> &mut crate::escape_processor::UnicodeEscapeCollector {
×
591
            unimplemented!("Mock doesn't need unicode collector")
×
592
        }
593

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

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

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

617
        // --- Methods from former EscapeHandler trait ---
618

NEW
619
        fn parser_state(&self) -> &crate::shared::State {
×
NEW
620
            &self.state
×
NEW
621
        }
×
622

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

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

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

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

640
    #[test]
641
    fn test_begin_events_key() {
1✔
642
        let mut context = MockContentExtractor::new();
1✔
643
        let event = crate::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, crate::shared::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 = crate::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, crate::shared::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 = crate::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, crate::shared::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 = crate::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, crate::shared::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 = crate::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, crate::shared::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(crate::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(crate::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(crate::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(crate::ujson::Event::ObjectStart),
1✔
742
            Some(crate::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(crate::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(crate::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