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

kaidokert / picojson-rs / 16710739468

03 Aug 2025 11:44PM UTC coverage: 93.537% (-0.9%) from 94.472%
16710739468

Pull #81

github

web-flow
Merge 089ae7d88 into 31328b537
Pull Request #81: Re-introduce Push parser

319 of 398 new or added lines in 5 files covered. (80.15%)

15 existing lines in 1 file now uncovered.

4979 of 5323 relevant lines covered (93.54%)

1315.67 hits per line

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

80.26
/picojson/src/push_parser.rs
1
// SPDX-License-Identifier: Apache-2.0
2

3
//! A SAX-style JSON push parser.
4
//!
5
//! Clean implementation based on handler_design pattern with proper HRTB lifetime management.
6

7
use crate::event_processor::{ContentExtractor, EscapeTiming, ParserCore};
8
use crate::push_content_builder::{PushContentExtractor, PushParserHandler};
9
use crate::shared::{DataSource, State};
10
use crate::stream_buffer::StreamBufferError;
11
use crate::{ujson, BitStackConfig, Event, ParseError};
12

13
#[cfg(any(test, debug_assertions))]
14
extern crate std;
15

16
/// A SAX-style JSON push parser.
17
///
18
/// Generic over BitStack storage type for configurable nesting depth. Parsing
19
/// events are returned to the handler.
20
///
21
/// # Generic Parameters
22
///
23
/// * `'scratch` - Lifetime for the scratch buffer used for temporary storage
24
/// * `H` - The event handler type that implements [`PushParserHandler`]
25
/// * `C` - BitStack configuration type that implements [`BitStackConfig`]
26
pub struct PushParser<'input, 'scratch, H, C>
27
where
28
    C: BitStackConfig,
29
{
30
    /// Content extractor that handles content extraction and event emission
31
    extractor: PushContentExtractor<'input, 'scratch>,
32
    /// The handler that receives events
33
    handler: H,
34
    /// Core parser logic shared with other parsers
35
    core: ParserCore<C::Bucket, C::Counter>,
36
}
37

38
impl<'input, 'scratch, H, C> PushParser<'input, 'scratch, H, C>
39
where
40
    C: BitStackConfig,
41
{
42
    /// Creates a new `PushParser`.
43
    pub fn new(handler: H, buffer: &'scratch mut [u8]) -> Self {
1,061✔
44
        Self {
1,061✔
45
            extractor: PushContentExtractor::new(buffer),
1,061✔
46
            handler,
1,061✔
47
            core: ParserCore::new_chunked(),
1,061✔
48
        }
1,061✔
49
    }
1,061✔
50

51
    /// Processes a chunk of input data.
52
    pub fn write<E>(&mut self, data: &'input [u8]) -> Result<(), PushParseError<E>>
6,518✔
53
    where
6,518✔
54
        H: for<'a, 'b> PushParserHandler<'a, 'b, E>,
6,518✔
55
        E: From<ParseError>,
6,518✔
56
    {
57
        // Apply any queued buffer resets
58
        self.extractor.apply_unescaped_reset_if_queued();
6,518✔
59

60
        // Set the input slice for the extractor to iterate over
61
        self.extractor.set_chunk(data);
6,518✔
62

63
        // Use ParserCore to process all bytes in the chunk
64
        loop {
65
            match self.core.next_event_impl_with_flags(
13,413✔
66
                &mut self.extractor,
13,413✔
67
                EscapeTiming::OnEnd, // PushParser uses OnEnd timing like StreamParser
13,413✔
68
                |extractor, byte| {
18,645✔
69
                    // Selective accumulation: let PushContentExtractor decide based on its state
70
                    // whether this byte should be accumulated or processed directly
71
                    extractor.handle_byte_accumulation(byte)
18,645✔
72
                },
18,645✔
73
                true, // always_accumulate_during_escapes: ensure all hex digits reach the accumulator
74
            ) {
75
                Ok(Event::EndDocument) => {
76
                    // EndDocument during write() means we've consumed all bytes in current chunk
NEW
77
                    break;
×
78
                }
79
                Ok(event) => {
6,895✔
80
                    // Handle all other events normally
81
                    self.handler
6,895✔
82
                        .handle_event(event)
6,895✔
83
                        .map_err(PushParseError::Handler)?;
6,895✔
84

85
                    // Apply any queued buffer resets after the event has been processed
86
                    // This ensures that buffer content from previous tokens doesn't leak into subsequent ones
87
                    self.extractor.apply_unescaped_reset_if_queued();
6,895✔
88
                }
89
                Err(ParseError::EndOfData) => {
90
                    // No more events available from current chunk
91
                    break;
6,418✔
92
                }
93
                Err(e) => {
100✔
94
                    return Err(PushParseError::Parse(e));
100✔
95
                }
96
            }
97
        }
98

99
        // Check for chunk boundary condition - if still processing a token when chunk ends
100
        let extractor_state = self.extractor.parser_state();
6,418✔
101

102
        if matches!(
2,626✔
103
            extractor_state,
6,418✔
104
            State::String(_) | State::Key(_) | State::Number(_)
105
        ) {
106
            // If we haven't already started using the scratch buffer (e.g., due to escapes)
107
            if !self.extractor.has_unescaped_content() {
3,792✔
108
                // Copy the partial content from this chunk to scratch buffer before it's lost
109
                self.extractor.copy_partial_content_to_scratch()?;
960✔
110
            } else {
111
                // Special case: For Numbers, check if the scratch buffer is actually empty
112
                // This handles the byte-by-byte case where the flag is stale from previous Key processing
113
                if matches!(extractor_state, State::Number(_)) {
2,832✔
114
                    let buffer_slice = self.extractor.get_unescaped_slice().unwrap_or(&[]);
715✔
115
                    let buffer_empty = buffer_slice.is_empty();
715✔
116

117
                    if buffer_empty {
715✔
NEW
118
                        self.extractor.copy_partial_content_to_scratch()?;
×
119
                    }
715✔
120
                }
2,117✔
121
            }
122
        }
2,626✔
123

124
        // Reset input slice
125
        self.extractor.reset_input();
6,409✔
126

127
        // Update position offset for next call
128
        self.extractor.add_position_offset(data.len());
6,409✔
129

130
        Ok(())
6,409✔
131
    }
6,518✔
132

133
    /// Finishes parsing, flushes any remaining events, and returns the handler.
134
    /// This method consumes the parser.
135
    pub fn finish<E>(mut self) -> Result<H, PushParseError<E>>
952✔
136
    where
952✔
137
        H: for<'a, 'b> PushParserHandler<'a, 'b, E>,
952✔
138
    {
139
        // Check that the JSON document is complete (all containers closed)
140
        // Use a no-op callback since we don't expect any more events
141
        let mut no_op_callback = |_event: ujson::Event, _pos: usize| {};
952✔
142
        let _bytes_processed = self.core.tokenizer.finish(&mut no_op_callback)?;
952✔
143

144
        // Handle any remaining content in the buffer
145
        if *self.extractor.parser_state() != State::None {
946✔
NEW
146
            return Err(crate::push_parser::PushParseError::Parse(
×
NEW
147
                ParseError::EndOfData,
×
NEW
148
            ));
×
149
        }
946✔
150

151
        // Emit EndDocument event
152
        self.handler
946✔
153
            .handle_event(Event::EndDocument)
946✔
154
            .map_err(PushParseError::Handler)?;
946✔
155

156
        Ok(self.handler)
946✔
157
    }
952✔
158
}
159

160
/// An error that can occur during push-based parsing.
161
#[derive(Debug, PartialEq)]
162
pub enum PushParseError<E> {
163
    /// An error occurred within the parser itself.
164
    Parse(ParseError),
165
    /// An error was returned by the user's handler.
166
    Handler(E),
167
}
168

169
impl<E> From<ujson::Error> for PushParseError<E> {
170
    fn from(e: ujson::Error) -> Self {
6✔
171
        PushParseError::Parse(e.into())
6✔
172
    }
6✔
173
}
174

175
impl<E> From<ParseError> for PushParseError<E> {
176
    fn from(e: ParseError) -> Self {
9✔
177
        PushParseError::Parse(e)
9✔
178
    }
9✔
179
}
180

181
impl<E> From<StreamBufferError> for PushParseError<E> {
NEW
182
    fn from(e: StreamBufferError) -> Self {
×
NEW
183
        PushParseError::Parse(e.into())
×
NEW
184
    }
×
185
}
186

187
impl<E> From<core::str::Utf8Error> for PushParseError<E> {
NEW
188
    fn from(e: core::str::Utf8Error) -> Self {
×
NEW
189
        PushParseError::Parse(ParseError::InvalidUtf8(e))
×
NEW
190
    }
×
191
}
192

193
// Implement From<ParseError> for common error types used in tests
194
// This needs to be globally accessible for integration tests, not just unit tests
195
#[cfg(any(test, debug_assertions))]
196
impl From<ParseError> for std::string::String {
197
    fn from(_: ParseError) -> Self {
×
198
        std::string::String::new()
×
199
    }
×
200
}
201

202
#[cfg(any(test, debug_assertions))]
203
impl From<ParseError> for () {
204
    fn from(_: ParseError) -> Self {}
×
205
}
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