Skip to main content

http_streams_core/
json_array_codec.rs

1//! Decoding a JSON array into its elements, incrementally.
2//!
3//! Elements of a JSON array are not self-delimiting: there is no marker that says "an element
4//! ends here" without tracking nesting, quoting and escaping. So this is a byte-level state
5//! machine rather than a delimiter search, and it handles objects, arrays, strings and bare
6//! primitives as elements.
7
8use crate::error::{StreamError, StreamErrorKind};
9use bytes::{Buf, BytesMut};
10use serde::Deserialize;
11use std::marker::PhantomData;
12
13/// A [`Decoder`](tokio_util::codec::Decoder) that yields the elements of a JSON array.
14#[derive(Clone, Debug)]
15pub struct JsonArrayCodec<T> {
16    max_length: usize,
17    json_cursor: JsonCursor,
18    /// `fn() -> T` rather than `T`: a bare `PhantomData<T>` would make this codec `!Send`
19    /// whenever `T` is, and the crates built on this one promise `Send` streams for item
20    /// types that carry no such bound. The item type is produced, never held, so this is also
21    /// the honest variance.
22    _ph: PhantomData<fn() -> T>,
23}
24
25#[derive(Clone, Debug)]
26struct JsonCursor {
27    current_offset: usize,
28    array_is_opened: bool,
29    delimiter_expected: bool,
30    quote_opened: bool,
31    escaped: bool,
32    opened_brackets: usize,
33    current_obj_pos: usize,
34    /// When `Some(pos)`, a primitive value (number/bool/null/string) is being accumulated from
35    /// `pos` in the buffer. A quoted string also uses this.
36    current_primitive_start: Option<usize>,
37}
38
39impl<T> JsonArrayCodec<T> {
40    /// A codec that rejects any single element longer than `max_length` bytes.
41    pub fn new_with_max_length(max_length: usize) -> Self {
42        let initial_cursor = JsonCursor {
43            current_offset: 0,
44            array_is_opened: false,
45            delimiter_expected: false,
46            quote_opened: false,
47            escaped: false,
48            opened_brackets: 0,
49            current_obj_pos: 0,
50            current_primitive_start: None,
51        };
52
53        JsonArrayCodec {
54            max_length,
55            json_cursor: initial_cursor,
56            _ph: PhantomData,
57        }
58    }
59}
60
61fn codec_error(err: serde_json::Error) -> StreamError {
62    StreamError::new(StreamErrorKind::CodecError, Some(Box::new(err)), None)
63}
64
65impl<T> tokio_util::codec::Decoder for JsonArrayCodec<T>
66where
67    T: for<'de> Deserialize<'de>,
68{
69    /// Always `Ok(_)`: every failure this format can have is terminal, because the cursor
70    /// tracks nesting across records and cannot resynchronise after a bad one.
71    type Item = Result<T, StreamError>;
72    type Error = StreamError;
73
74    fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, StreamError> {
75        if buf.is_empty() {
76            return Ok(None);
77        }
78
79        for (position, current_ch) in buf[self.json_cursor.current_offset..buf.len()]
80            .iter()
81            .enumerate()
82        {
83            let abs_pos = self.json_cursor.current_offset + position;
84
85            if abs_pos >= self.max_length {
86                return Err(StreamError::new(
87                    StreamErrorKind::MaxLenReachedError,
88                    None,
89                    Some("Max object length reached".into()),
90                ));
91            }
92
93            match *current_ch {
94                b'[' if !self.json_cursor.quote_opened && self.json_cursor.opened_brackets == 0 => {
95                    if self.json_cursor.array_is_opened {
96                        // A nested array element, treated like an object open.
97                        self.json_cursor.current_obj_pos = abs_pos;
98                        self.json_cursor.opened_brackets += 1;
99                        self.json_cursor.current_primitive_start = None;
100                    } else {
101                        self.json_cursor.array_is_opened = true;
102                    }
103                }
104                b'[' if !self.json_cursor.quote_opened && self.json_cursor.opened_brackets > 0 => {
105                    self.json_cursor.opened_brackets += 1;
106                    self.json_cursor.escaped = false;
107                }
108                b']' if !self.json_cursor.quote_opened && self.json_cursor.opened_brackets == 0 => {
109                    // End of the top-level array. Emit any pending primitive.
110                    if let Some(prim_start) = self.json_cursor.current_primitive_start.take() {
111                        let obj_slice = trim_ascii(&buf[prim_start..abs_pos]);
112                        if !obj_slice.is_empty() {
113                            let result = serde_json::from_slice(obj_slice).map_err(codec_error);
114                            buf.advance(abs_pos + 1);
115                            self.json_cursor.current_offset = 0;
116                            self.json_cursor.delimiter_expected = false;
117                            return result.map(|item| Some(Ok(item)));
118                        }
119                    }
120                }
121                b']' if !self.json_cursor.quote_opened && self.json_cursor.opened_brackets > 0 => {
122                    self.json_cursor.opened_brackets -= 1;
123                    self.json_cursor.escaped = false;
124                    if self.json_cursor.opened_brackets == 0 {
125                        // Closed a nested array/object element.
126                        self.json_cursor.delimiter_expected = true;
127                        let obj_slice = &buf[self.json_cursor.current_obj_pos..abs_pos + 1];
128                        let result = serde_json::from_slice(obj_slice).map_err(codec_error);
129                        self.json_cursor.current_obj_pos = 0;
130                        buf.advance(abs_pos + 1);
131                        self.json_cursor.current_offset = 0;
132                        return result.map(|item| Some(Ok(item)));
133                    }
134                }
135                b'"' if !self.json_cursor.escaped && self.json_cursor.opened_brackets == 0 => {
136                    if self.json_cursor.quote_opened {
137                        // Closing quote of a top-level string element.
138                        self.json_cursor.quote_opened = false;
139                        if let Some(prim_start) = self.json_cursor.current_primitive_start.take() {
140                            self.json_cursor.delimiter_expected = true;
141                            let obj_slice = &buf[prim_start..abs_pos + 1];
142                            let result = serde_json::from_slice(obj_slice).map_err(codec_error);
143                            buf.advance(abs_pos + 1);
144                            self.json_cursor.current_offset = 0;
145                            return result.map(|item| Some(Ok(item)));
146                        }
147                    } else {
148                        // Opening quote of a top-level string element.
149                        self.json_cursor.quote_opened = true;
150                        if self.json_cursor.current_primitive_start.is_none() {
151                            self.json_cursor.current_primitive_start = Some(abs_pos);
152                        }
153                    }
154                }
155                b'"' if !self.json_cursor.escaped => {
156                    // Inside a nested object/array.
157                    self.json_cursor.quote_opened = !self.json_cursor.quote_opened;
158                }
159                b'\\' if self.json_cursor.quote_opened => {
160                    self.json_cursor.escaped = !self.json_cursor.escaped;
161                }
162                b'{' if !self.json_cursor.quote_opened => {
163                    if self.json_cursor.opened_brackets == 0 {
164                        self.json_cursor.current_obj_pos = abs_pos;
165                        self.json_cursor.current_primitive_start = None;
166                    }
167                    self.json_cursor.opened_brackets += 1;
168                    self.json_cursor.escaped = false;
169                }
170                // Guarded like the `]` arm above. Without this, a stray `}` at the top level
171                // underflows the counter: a panic in debug, and in release a wrap to
172                // `usize::MAX` that silently corrupts framing for the rest of the stream.
173                b'}' if !self.json_cursor.quote_opened && self.json_cursor.opened_brackets == 0 => {
174                    return Err(StreamError::new(
175                        StreamErrorKind::CodecError,
176                        None,
177                        Some("Unexpected `}` outside any object".into()),
178                    ));
179                }
180                b'}' if !self.json_cursor.quote_opened => {
181                    self.json_cursor.opened_brackets -= 1;
182                    self.json_cursor.escaped = false;
183                    if self.json_cursor.opened_brackets == 0 {
184                        self.json_cursor.delimiter_expected = true;
185                        let obj_slice = &buf[self.json_cursor.current_obj_pos..abs_pos + 1];
186                        let result = serde_json::from_slice(obj_slice).map_err(codec_error);
187                        self.json_cursor.current_obj_pos = 0;
188                        buf.advance(abs_pos + 1);
189                        self.json_cursor.current_offset = 0;
190                        return result.map(|item| Some(Ok(item)));
191                    }
192                }
193                b',' if !self.json_cursor.quote_opened && self.json_cursor.opened_brackets == 0 => {
194                    if let Some(prim_start) = self.json_cursor.current_primitive_start.take() {
195                        let obj_slice = trim_ascii(&buf[prim_start..abs_pos]);
196                        if !obj_slice.is_empty() {
197                            let result = serde_json::from_slice(obj_slice).map_err(codec_error);
198                            buf.advance(abs_pos + 1);
199                            self.json_cursor.current_offset = 0;
200                            self.json_cursor.delimiter_expected = false;
201                            return result.map(|item| Some(Ok(item)));
202                        }
203                    } else if !self.json_cursor.delimiter_expected {
204                        return Err(StreamError::new(
205                            StreamErrorKind::CodecError,
206                            None,
207                            Some("Unexpected delimiter found".into()),
208                        ));
209                    }
210                    self.json_cursor.delimiter_expected = false;
211                }
212                _ if !self.json_cursor.quote_opened
213                    && self.json_cursor.opened_brackets == 0
214                    && self.json_cursor.array_is_opened
215                    && !current_ch.is_ascii_whitespace() =>
216                {
217                    // Non-whitespace at top level inside the array: the start of a primitive.
218                    if self.json_cursor.current_primitive_start.is_none() {
219                        self.json_cursor.current_primitive_start = Some(abs_pos);
220                    }
221                    self.json_cursor.escaped = false;
222                }
223                _ => {
224                    self.json_cursor.escaped = false;
225                }
226            }
227        }
228        self.json_cursor.current_offset = buf.len();
229
230        Ok(None)
231    }
232
233    fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, StreamError> {
234        self.decode(buf)
235    }
236}
237
238fn trim_ascii(bytes: &[u8]) -> &[u8] {
239    let start = bytes
240        .iter()
241        .position(|b| !b.is_ascii_whitespace())
242        .unwrap_or(bytes.len());
243    let end = bytes
244        .iter()
245        .rposition(|b| !b.is_ascii_whitespace())
246        .map(|i| i + 1)
247        .unwrap_or(0);
248    if start >= end {
249        &[]
250    } else {
251        &bytes[start..end]
252    }
253}