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

kaidokert / picojson-rs / 16239454628

12 Jul 2025 03:29PM UTC coverage: 94.234% (+0.5%) from 93.685%
16239454628

Pull #53

github

web-flow
Merge a5de05240 into 1db0180ce
Pull Request #53: Big refactor step 1

433 of 447 new or added lines in 3 files covered. (96.87%)

1 existing line in 1 file now uncovered.

4658 of 4943 relevant lines covered (94.23%)

624.61 hits per line

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

97.15
/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
/// Parser context trait for abstracting common operations
12
pub trait ParserContext {
13
    /// Get current position in the input
14
    fn current_position(&self) -> usize;
15

16
    /// Begin string/key content processing at current position
17
    fn begin_string_content(&mut self, pos: usize);
18

19
    /// Set parser state
20
    fn set_parser_state(&mut self, state: crate::shared::State);
21
}
22

23
/// Escape handling trait for abstracting escape sequence processing between parsers
24
pub trait EscapeHandler {
25
    /// Get the current parser state for escape context checking
26
    fn parser_state(&self) -> &crate::shared::State;
27

28
    /// Reset all unicode escape collector state (including pending surrogates)
29
    fn reset_unicode_collector_all(&mut self);
30

31
    /// Reset unicode escape collector for new sequence (preserving pending surrogates)
32
    fn reset_unicode_collector(&mut self);
33

34
    /// Check if there's a pending high surrogate waiting for low surrogate
35
    fn has_pending_high_surrogate(&self) -> bool;
36

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

40
    /// Handle a simple escape character (after EscapeProcessor conversion)
41
    fn handle_simple_escape_char(&mut self, escape_char: u8) -> Result<(), crate::ParseError>;
42
}
43

44
/// Minimal buffer interface for position tracking and content extraction
45
pub trait BufferLike {
46
    /// Current position in the input stream/slice
47
    fn current_position(&self) -> usize;
48

49
    /// Extract a slice of bytes from start to end position
50
    /// Used for number and string content extraction
51
    fn get_slice(&self, start: usize, end: usize) -> Result<&[u8], ParseError>;
52
}
53

54
/// Result of processing a tokenizer event
55
#[derive(Debug)]
56
pub enum EventResult<'a, 'b> {
57
    /// Event processing is complete, return this event to the user
58
    Complete(Event<'a, 'b>),
59
    /// Continue processing more tokenizer events
60
    Continue,
61
    /// Extract string content (delegate to parser-specific logic)
62
    ExtractString,
63
    /// Extract key content (delegate to parser-specific logic)
64
    ExtractKey,
65
    /// Extract number content (delegate to parser-specific logic)
66
    /// bool indicates if number was terminated by container delimiter
67
    ExtractNumber(bool),
68
}
69

70
/// Process Begin events that have similar patterns between parsers
71
pub fn process_begin_events<C: ParserContext>(
4,381✔
72
    event: &crate::ujson::Event,
4,381✔
73
    context: &mut C,
4,381✔
74
) -> Option<EventResult<'static, 'static>> {
4,381✔
75
    use crate::shared::{ContentRange, State};
76

77
    match event {
3,494✔
78
        // String/Key Begin events - nearly identical patterns
79
        crate::ujson::Event::Begin(EventToken::Key) => {
80
            let pos = context.current_position();
360✔
81
            context.set_parser_state(State::Key(pos));
360✔
82
            context.begin_string_content(pos);
360✔
83
            Some(EventResult::Continue)
360✔
84
        }
85
        crate::ujson::Event::Begin(EventToken::String) => {
86
            let pos = context.current_position();
770✔
87
            context.set_parser_state(State::String(pos));
770✔
88
            context.begin_string_content(pos);
770✔
89
            Some(EventResult::Continue)
770✔
90
        }
91

92
        // Number Begin events - identical logic
93
        crate::ujson::Event::Begin(
94
            EventToken::Number | EventToken::NumberAndArray | EventToken::NumberAndObject,
95
        ) => {
96
            let pos = context.current_position();
477✔
97
            let number_start = ContentRange::number_start_from_current(pos);
477✔
98
            context.set_parser_state(State::Number(number_start));
477✔
99
            Some(EventResult::Continue)
477✔
100
        }
101

102
        // Primitive Begin events - identical logic
103
        crate::ujson::Event::Begin(EventToken::True | EventToken::False | EventToken::Null) => {
104
            Some(EventResult::Continue)
28✔
105
        }
106

107
        _ => None,
2,746✔
108
    }
109
}
4,381✔
110

111
/// Clear event storage array - utility function
112
pub fn clear_events(event_storage: &mut [Option<crate::ujson::Event>; 2]) {
18,786✔
113
    event_storage[0] = None;
18,786✔
114
    event_storage[1] = None;
18,786✔
115
}
18,786✔
116

117
/// Trait for content extraction operations that differ between parsers
118
pub trait ContentExtractor: EscapeHandler {
119
    /// Get mutable access to parser state
120
    fn parser_state_mut(&mut self) -> &mut crate::shared::State;
121

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

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

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

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

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

153
        *self.parser_state_mut() = crate::shared::State::None;
430✔
154
        self.extract_string_content(start_pos)
430✔
155
    }
433✔
156

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

164
        // Check for incomplete surrogate pairs before ending the key
165
        if self.has_pending_high_surrogate() {
329✔
NEW
166
            return Err(crate::ParseError::InvalidUnicodeCodepoint);
×
167
        }
329✔
168

169
        *self.parser_state_mut() = crate::shared::State::None;
329✔
170
        self.extract_key_content(start_pos)
329✔
171
    }
329✔
172

173
    /// Shared validation and extraction for number content
174
    fn validate_and_extract_number(
282✔
175
        &mut self,
282✔
176
        from_container_end: bool,
282✔
177
    ) -> Result<crate::Event<'_, '_>, crate::ParseError> {
282✔
178
        let start_pos = match *self.parser_state() {
282✔
179
            crate::shared::State::Number(pos) => pos,
282✔
NEW
180
            _ => return Err(crate::shared::UnexpectedState::StateMismatch.into()),
×
181
        };
182

183
        *self.parser_state_mut() = crate::shared::State::None;
282✔
184
        self.extract_number_content(start_pos, from_container_end)
282✔
185
    }
282✔
186
}
187

188
/// Creates a standard tokenizer callback for event storage
189
///
190
/// This callback stores tokenizer events in the parser's event array, filling the first
191
/// available slot. This pattern is identical across both SliceParser and StreamParser.
192
pub fn create_tokenizer_callback<'a>(
21,065✔
193
    event_storage: &'a mut [Option<crate::ujson::Event>; 2],
21,065✔
194
) -> impl FnMut(crate::ujson::Event, usize) + 'a {
21,065✔
195
    |event, _len| {
8,599✔
196
        for evt in event_storage.iter_mut() {
9,516✔
197
            if evt.is_none() {
9,516✔
198
                *evt = Some(event);
8,598✔
199
                return;
8,598✔
200
            }
918✔
201
        }
202
    }
8,599✔
203
}
21,065✔
204

205
/// Shared utility to check if any events are waiting to be processed
206
pub fn have_events(event_storage: &[Option<crate::ujson::Event>; 2]) -> bool {
48,972✔
207
    event_storage.iter().any(|evt| evt.is_some())
83,197✔
208
}
48,972✔
209

210
/// Shared utility to extract the first available event from storage
211
pub fn take_first_event(
8,588✔
212
    event_storage: &mut [Option<crate::ujson::Event>; 2],
8,588✔
213
) -> Option<crate::ujson::Event> {
8,588✔
214
    event_storage.iter_mut().find_map(|e| e.take())
9,495✔
215
}
8,588✔
216

217
/// Process simple escape sequence events that have similar patterns between parsers
218
pub fn process_simple_escape_event<E: EscapeHandler>(
688✔
219
    escape_token: &EventToken,
688✔
220
    escape_handler: &mut E,
688✔
221
) -> Result<(), crate::ParseError> {
688✔
222
    // Clear any pending high surrogate state when we encounter a simple escape
223
    // This ensures that interrupted surrogate pairs (like \uD801\n\uDC37) are properly rejected
224
    escape_handler.reset_unicode_collector_all();
688✔
225

226
    // Use unified escape token processing from EscapeProcessor
227
    let unescaped_char =
688✔
228
        crate::escape_processor::EscapeProcessor::process_escape_token(escape_token)?;
688✔
229

230
    // Only process if we're inside a string or key
231
    match escape_handler.parser_state() {
688✔
232
        crate::shared::State::String(_) | crate::shared::State::Key(_) => {
233
            escape_handler.handle_simple_escape_char(unescaped_char)?;
688✔
234
        }
NEW
235
        _ => {} // Ignore if not in string/key context
×
236
    }
237

238
    Ok(())
686✔
239
}
688✔
240

241
/// Process Unicode escape begin/end events that have similar patterns between parsers
242
pub fn process_unicode_escape_events<E: EscapeHandler>(
1,163✔
243
    event: &crate::ujson::Event,
1,163✔
244
    escape_handler: &mut E,
1,163✔
245
) -> Result<bool, crate::ParseError> {
1,163✔
246
    match event {
944✔
247
        crate::ujson::Event::Begin(EventToken::UnicodeEscape) => {
248
            // Start Unicode escape collection - reset collector for new sequence
249
            // Only handle if we're inside a string or key
250
            match escape_handler.parser_state() {
219✔
251
                crate::shared::State::String(_) | crate::shared::State::Key(_) => {
219✔
252
                    escape_handler.reset_unicode_collector();
219✔
253
                }
219✔
NEW
254
                _ => {} // Ignore if not in string/key context
×
255
            }
256
            Ok(true) // Event was handled
219✔
257
        }
258
        crate::ujson::Event::End(EventToken::UnicodeEscape) => {
259
            // Handle end of Unicode escape sequence (\uXXXX)
260
            match escape_handler.parser_state() {
201✔
261
                crate::shared::State::String(_) | crate::shared::State::Key(_) => {
262
                    escape_handler.process_unicode_escape_with_collector()?;
201✔
263
                }
NEW
264
                _ => {} // Ignore if not in string/key context
×
265
            }
266
            Ok(true) // Event was handled
177✔
267
        }
268
        _ => Ok(false), // Event was not handled
743✔
269
    }
270
}
1,163✔
271

272
/// Process simple container and primitive events that are identical between parsers
273
pub fn process_simple_events(event: crate::ujson::Event) -> Option<EventResult<'static, 'static>> {
8,594✔
274
    match event {
1,961✔
275
        // Container events - identical processing
276
        crate::ujson::Event::ObjectStart => Some(EventResult::Complete(Event::StartObject)),
254✔
277
        crate::ujson::Event::ObjectEnd => Some(EventResult::Complete(Event::EndObject)),
209✔
278
        crate::ujson::Event::ArrayStart => Some(EventResult::Complete(Event::StartArray)),
1,672✔
279
        crate::ujson::Event::ArrayEnd => Some(EventResult::Complete(Event::EndArray)),
1,009✔
280

281
        // Primitive values - identical processing
282
        crate::ujson::Event::End(EventToken::True) => {
283
            Some(EventResult::Complete(Event::Bool(true)))
12✔
284
        }
285
        crate::ujson::Event::End(EventToken::False) => {
286
            Some(EventResult::Complete(Event::Bool(false)))
7✔
287
        }
288
        crate::ujson::Event::End(EventToken::Null) => Some(EventResult::Complete(Event::Null)),
8✔
289

290
        // Content extraction triggers - identical logic
291
        crate::ujson::Event::End(EventToken::String) => Some(EventResult::ExtractString),
434✔
292
        crate::ujson::Event::End(EventToken::Key) => Some(EventResult::ExtractKey),
329✔
293
        crate::ujson::Event::End(EventToken::Number) => Some(EventResult::ExtractNumber(false)),
56✔
294
        crate::ujson::Event::End(EventToken::NumberAndArray) => {
295
            Some(EventResult::ExtractNumber(true))
142✔
296
        }
297
        crate::ujson::Event::End(EventToken::NumberAndObject) => {
298
            Some(EventResult::ExtractNumber(true))
86✔
299
        }
300

301
        // All other events need parser-specific handling
302
        _ => None,
4,376✔
303
    }
304
}
8,594✔
305

306
#[cfg(test)]
307
mod tests {
308
    use super::*;
309

310
    #[test]
311
    fn test_container_events() {
1✔
312
        assert!(matches!(
1✔
313
            process_simple_events(crate::ujson::Event::ObjectStart),
1✔
314
            Some(EventResult::Complete(Event::StartObject))
315
        ));
316

317
        assert!(matches!(
1✔
318
            process_simple_events(crate::ujson::Event::ArrayEnd),
1✔
319
            Some(EventResult::Complete(Event::EndArray))
320
        ));
321
    }
1✔
322

323
    #[test]
324
    fn test_primitive_events() {
1✔
325
        assert!(matches!(
1✔
326
            process_simple_events(crate::ujson::Event::End(EventToken::True)),
1✔
327
            Some(EventResult::Complete(Event::Bool(true)))
328
        ));
329

330
        assert!(matches!(
1✔
331
            process_simple_events(crate::ujson::Event::End(EventToken::Null)),
1✔
332
            Some(EventResult::Complete(Event::Null))
333
        ));
334
    }
1✔
335

336
    #[test]
337
    fn test_extraction_triggers() {
1✔
338
        assert!(matches!(
1✔
339
            process_simple_events(crate::ujson::Event::End(EventToken::String)),
1✔
340
            Some(EventResult::ExtractString)
341
        ));
342

343
        assert!(matches!(
1✔
344
            process_simple_events(crate::ujson::Event::End(EventToken::Number)),
1✔
345
            Some(EventResult::ExtractNumber(false))
346
        ));
347

348
        assert!(matches!(
1✔
349
            process_simple_events(crate::ujson::Event::End(EventToken::NumberAndArray)),
1✔
350
            Some(EventResult::ExtractNumber(true))
351
        ));
352
    }
1✔
353

354
    #[test]
355
    fn test_complex_events_not_handled() {
1✔
356
        assert!(process_simple_events(crate::ujson::Event::Begin(EventToken::String)).is_none());
1✔
357
        assert!(
1✔
358
            process_simple_events(crate::ujson::Event::Begin(EventToken::EscapeQuote)).is_none()
1✔
359
        );
360
    }
1✔
361

362
    // Mock ParserContext for testing
363
    struct MockParserContext {
364
        position: usize,
365
        state: Option<crate::shared::State>,
366
        string_begin_calls: Vec<usize>,
367
    }
368

369
    impl MockParserContext {
370
        fn new() -> Self {
5✔
371
            Self {
5✔
372
                position: 42,
5✔
373
                state: None,
5✔
374
                string_begin_calls: Vec::new(),
5✔
375
            }
5✔
376
        }
5✔
377
    }
378

379
    impl ParserContext for MockParserContext {
380
        fn current_position(&self) -> usize {
3✔
381
            self.position
3✔
382
        }
3✔
383

384
        fn begin_string_content(&mut self, pos: usize) {
2✔
385
            self.string_begin_calls.push(pos);
2✔
386
        }
2✔
387

388
        fn set_parser_state(&mut self, state: crate::shared::State) {
3✔
389
            self.state = Some(state);
3✔
390
        }
3✔
391
    }
392

393
    #[test]
394
    fn test_begin_events_key() {
1✔
395
        let mut context = MockParserContext::new();
1✔
396
        let event = crate::ujson::Event::Begin(EventToken::Key);
1✔
397

398
        let result = process_begin_events(&event, &mut context);
1✔
399

400
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
401
        assert!(matches!(context.state, Some(crate::shared::State::Key(42))));
1✔
402
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
403
    }
1✔
404

405
    #[test]
406
    fn test_begin_events_string() {
1✔
407
        let mut context = MockParserContext::new();
1✔
408
        let event = crate::ujson::Event::Begin(EventToken::String);
1✔
409

410
        let result = process_begin_events(&event, &mut context);
1✔
411

412
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
413
        assert!(matches!(
1✔
414
            context.state,
1✔
415
            Some(crate::shared::State::String(42))
416
        ));
417
        assert_eq!(context.string_begin_calls, vec![42]);
1✔
418
    }
1✔
419

420
    #[test]
421
    fn test_begin_events_number() {
1✔
422
        let mut context = MockParserContext::new();
1✔
423
        let event = crate::ujson::Event::Begin(EventToken::Number);
1✔
424

425
        let result = process_begin_events(&event, &mut context);
1✔
426

427
        assert!(matches!(result, Some(EventResult::Continue)));
1✔
428
        // Number should get position adjusted by ContentRange::number_start_from_current
429
        assert!(matches!(
1✔
430
            context.state,
1✔
431
            Some(crate::shared::State::Number(_))
432
        ));
433
        assert_eq!(context.string_begin_calls, Vec::<usize>::new()); // No string calls for numbers
1✔
434
    }
1✔
435

436
    #[test]
437
    fn test_begin_events_primitives() {
1✔
438
        let mut context = MockParserContext::new();
1✔
439

440
        for token in [EventToken::True, EventToken::False, EventToken::Null] {
3✔
441
            let event = crate::ujson::Event::Begin(token);
3✔
442
            let result = process_begin_events(&event, &mut context);
3✔
443
            assert!(matches!(result, Some(EventResult::Continue)));
3✔
444
        }
445

446
        // Should not affect state or string processing
447
        assert!(context.state.is_none());
1✔
448
        assert!(context.string_begin_calls.is_empty());
1✔
449
    }
1✔
450

451
    #[test]
452
    fn test_begin_events_not_handled() {
1✔
453
        let mut context = MockParserContext::new();
1✔
454
        let event = crate::ujson::Event::Begin(EventToken::EscapeQuote);
1✔
455

456
        let result = process_begin_events(&event, &mut context);
1✔
457

458
        assert!(result.is_none());
1✔
459
        assert!(context.state.is_none());
1✔
460
        assert!(context.string_begin_calls.is_empty());
1✔
461
    }
1✔
462

463
    #[test]
464
    fn test_tokenizer_callback() {
1✔
465
        let mut event_storage = [None, None];
1✔
466

467
        // Initially no events
468
        assert!(!have_events(&event_storage));
1✔
469

470
        {
1✔
471
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
472

1✔
473
            // Add first event
1✔
474
            callback(crate::ujson::Event::ObjectStart, 1);
1✔
475
        }
1✔
476
        assert!(have_events(&event_storage));
1✔
477
        assert!(event_storage[0].is_some());
1✔
478
        assert!(event_storage[1].is_none());
1✔
479

480
        {
1✔
481
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
482
            // Add second event
1✔
483
            callback(crate::ujson::Event::ArrayStart, 1);
1✔
484
        }
1✔
485
        assert!(event_storage[0].is_some());
1✔
486
        assert!(event_storage[1].is_some());
1✔
487

488
        {
1✔
489
            let mut callback = create_tokenizer_callback(&mut event_storage);
1✔
490
            // Storage is full, third event should be ignored (no panic)
1✔
491
            callback(crate::ujson::Event::ObjectEnd, 1);
1✔
492
        }
1✔
493
        assert!(event_storage[0].is_some());
1✔
494
        assert!(event_storage[1].is_some());
1✔
495
    }
1✔
496

497
    #[test]
498
    fn test_event_extraction() {
1✔
499
        let mut event_storage = [
1✔
500
            Some(crate::ujson::Event::ObjectStart),
1✔
501
            Some(crate::ujson::Event::ArrayStart),
1✔
502
        ];
1✔
503

504
        // Extract first event
505
        let first = take_first_event(&mut event_storage);
1✔
506
        assert!(matches!(first, Some(crate::ujson::Event::ObjectStart)));
1✔
507
        assert!(event_storage[0].is_none());
1✔
508
        assert!(event_storage[1].is_some());
1✔
509

510
        // Extract second event
511
        let second = take_first_event(&mut event_storage);
1✔
512
        assert!(matches!(second, Some(crate::ujson::Event::ArrayStart)));
1✔
513
        assert!(event_storage[0].is_none());
1✔
514
        assert!(event_storage[1].is_none());
1✔
515

516
        // No more events
517
        let none = take_first_event(&mut event_storage);
1✔
518
        assert!(none.is_none());
1✔
519
        assert!(!have_events(&event_storage));
1✔
520
    }
1✔
521
}
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