Skip to main content

uqa_core/
json.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Borrowed JSON events share one grammar without choosing a numeric or object representation.
8
9use crate::{
10    memory::{Budgeted, BudgetedSmallVec, MemoryBudget, MemoryError, ProductionControl},
11    CancellationToken, QueryCancelled,
12};
13
14mod lexical;
15mod string;
16
17pub use string::{decode_json_string, decode_json_string_with_control};
18
19#[derive(Debug, thiserror::Error)]
20pub enum JsonReadError {
21    #[error("invalid JSON text")]
22    InvalidJson,
23    #[error(transparent)]
24    Memory(#[from] MemoryError),
25    #[error(transparent)]
26    Cancelled(#[from] QueryCancelled),
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum JsonToken<'a> {
31    Null,
32    Bool(bool),
33    Number(&'a str),
34    /// The validated token includes its quotes and escapes.
35    String(&'a [u8]),
36    Key(&'a [u8]),
37    StartArray,
38    EndArray,
39    StartObject,
40    EndObject,
41}
42
43#[derive(Debug, PartialEq, Eq)]
44pub struct JsonEvent<'a> {
45    pub token: JsonToken<'a>,
46    /// Byte offsets in the original text, including quotes or a container delimiter.
47    pub range: std::ops::Range<usize>,
48}
49
50#[derive(Clone, Copy)]
51enum Frame {
52    ArrayFirst,
53    ArrayValue,
54    ArrayAfter,
55    ObjectFirst,
56    ObjectKey,
57    ObjectValue,
58    ObjectAfter,
59}
60
61/// Open containers. A bounded stack keeps ordinary nesting inline and charges only deeper nesting.
62enum Stack {
63    Unbounded(Vec<Frame>),
64    Bounded(BudgetedSmallVec<[Frame; 16]>),
65}
66
67impl Stack {
68    fn frames(&mut self) -> &mut [Frame] {
69        match self {
70            Self::Unbounded(values) => values,
71            Self::Bounded(values) => values,
72        }
73    }
74
75    fn push(&mut self, frame: Frame) -> Result<(), JsonReadError> {
76        match self {
77            Self::Unbounded(values) => values.push(frame),
78            Self::Bounded(values) => values.push(frame)?,
79        }
80        Ok(())
81    }
82
83    fn pop(&mut self) {
84        match self {
85            Self::Unbounded(values) => {
86                values.pop();
87            }
88            Self::Bounded(values) => {
89                values.pop();
90            }
91        }
92    }
93}
94
95/// Iterative structural decoding charges its nesting stack before allocation. Event payloads borrow the input; consumers own and charge their chosen value representation separately.
96pub struct JsonReader<'a, 'c> {
97    input: &'a [u8],
98    position: usize,
99    stack: Stack,
100    cancellation: Option<&'c CancellationToken>,
101    production: Option<ProductionControl<'c>>,
102    root_started: bool,
103    depth_limit: Option<usize>,
104    ignored_string_escapes: bool,
105    /// The input came from a `str`, so every token between ASCII delimiters is already valid UTF-8.
106    validated_utf8: bool,
107}
108
109impl<'a, 'c> JsonReader<'a, 'c> {
110    pub fn new(input: &'a str, memory: &MemoryBudget, cancellation: &'c CancellationToken) -> Self {
111        Self {
112            validated_utf8: true,
113            ..Self::from_slice(input.as_bytes(), memory, cancellation)
114        }
115    }
116
117    pub fn from_slice(
118        input: &'a [u8],
119        memory: &MemoryBudget,
120        cancellation: &'c CancellationToken,
121    ) -> Self {
122        Self {
123            input,
124            position: 0,
125            stack: Stack::Bounded(BudgetedSmallVec::new(memory)),
126            cancellation: Some(cancellation),
127            production: None,
128            root_started: false,
129            depth_limit: None,
130            ignored_string_escapes: false,
131            validated_utf8: false,
132        }
133    }
134
135    pub(crate) fn unbounded(input: &'a str) -> Self {
136        Self {
137            input: input.as_bytes(),
138            position: 0,
139            stack: Stack::Unbounded(Vec::new()),
140            cancellation: None,
141            production: None,
142            root_started: false,
143            depth_limit: None,
144            ignored_string_escapes: false,
145            validated_utf8: true,
146        }
147    }
148
149    /// Read the same borrowed grammar under an ordinary or controlled producer, preserving every active cancellation owner within token scans as well as between events.
150    pub fn with_control(input: &'a str, control: &ProductionControl<'c>) -> Self {
151        Self {
152            input: input.as_bytes(),
153            position: 0,
154            stack: control.budget().map_or_else(
155                || Stack::Unbounded(Vec::new()),
156                |budget| Stack::Bounded(BudgetedSmallVec::new(budget)),
157            ),
158            cancellation: None,
159            production: Some(*control),
160            root_started: false,
161            depth_limit: None,
162            ignored_string_escapes: false,
163            validated_utf8: true,
164        }
165    }
166
167    /// Limit open containers for formats whose previous decoder imposed a nesting limit.
168    #[must_use]
169    pub fn with_depth_limit(mut self, limit: usize) -> Self {
170        self.depth_limit = Some(limit);
171        self
172    }
173
174    /// Match serde's ignored-value byte validation when locating fields in a durable envelope: require four hexadecimal digits after each Unicode escape, without decoding UTF-8 or requiring surrogate pairs in discarded strings. Consumers must decode retained keys and string values with their ordinary strict decoder.
175    #[must_use]
176    pub fn with_ignored_string_escapes(mut self) -> Self {
177        self.ignored_string_escapes = true;
178        self
179    }
180
181    pub fn next_event(&mut self) -> Result<Option<JsonEvent<'a>>, JsonReadError> {
182        self.check()?;
183        self.skip_whitespace()?;
184        let frame = self.stack.frames().last().copied();
185        match frame {
186            None if self.root_started => {
187                return if self.position == self.input.len() {
188                    Ok(None)
189                } else {
190                    Err(JsonReadError::InvalidJson)
191                };
192            }
193            None => self.root_started = true,
194            Some(Frame::ArrayFirst) if self.peek() == Some(b']') => {
195                return self.close(JsonToken::EndArray)
196            }
197            Some(Frame::ObjectFirst) if self.peek() == Some(b'}') => {
198                return self.close(JsonToken::EndObject)
199            }
200            Some(Frame::ArrayAfter) => match self.peek() {
201                Some(b']') => return self.close(JsonToken::EndArray),
202                Some(b',') => {
203                    self.advance()?;
204                    self.skip_whitespace()?;
205                    self.replace(Frame::ArrayValue);
206                }
207                _ => return Err(JsonReadError::InvalidJson),
208            },
209            Some(Frame::ObjectAfter) => match self.peek() {
210                Some(b'}') => return self.close(JsonToken::EndObject),
211                Some(b',') => {
212                    self.advance()?;
213                    self.skip_whitespace()?;
214                    self.replace(Frame::ObjectKey);
215                }
216                _ => return Err(JsonReadError::InvalidJson),
217            },
218            _ => {}
219        }
220        match self.stack.frames().last().copied() {
221            Some(Frame::ObjectFirst | Frame::ObjectKey) => {
222                let start = self.position;
223                let key = self.string()?;
224                self.replace(Frame::ObjectValue);
225                return Ok(Some(self.event(start, JsonToken::Key(key))));
226            }
227            Some(Frame::ObjectValue) => {
228                self.consume(b':')?;
229                self.skip_whitespace()?;
230                self.replace(Frame::ObjectAfter);
231            }
232            Some(Frame::ArrayFirst | Frame::ArrayValue) => self.replace(Frame::ArrayAfter),
233            _ => {}
234        }
235        self.value().map(Some)
236    }
237
238    fn value(&mut self) -> Result<JsonEvent<'a>, JsonReadError> {
239        let start = self.position;
240        let token = match self.peek().ok_or(JsonReadError::InvalidJson)? {
241            b'n' => {
242                self.keyword(b"null")?;
243                JsonToken::Null
244            }
245            b't' => {
246                self.keyword(b"true")?;
247                JsonToken::Bool(true)
248            }
249            b'f' => {
250                self.keyword(b"false")?;
251                JsonToken::Bool(false)
252            }
253            b'"' => JsonToken::String(self.string()?),
254            b'-' | b'0'..=b'9' => JsonToken::Number(self.number()?),
255            b'[' | b'{' => {
256                if self
257                    .depth_limit
258                    .is_some_and(|limit| self.stack.frames().len() >= limit)
259                {
260                    return Err(JsonReadError::InvalidJson);
261                }
262                let array = self.peek() == Some(b'[');
263                self.stack.push(if array {
264                    Frame::ArrayFirst
265                } else {
266                    Frame::ObjectFirst
267                })?;
268                self.advance()?;
269                if array {
270                    JsonToken::StartArray
271                } else {
272                    JsonToken::StartObject
273                }
274            }
275            _ => return Err(JsonReadError::InvalidJson),
276        };
277        self.check()?;
278        Ok(self.event(start, token))
279    }
280
281    fn replace(&mut self, frame: Frame) {
282        *self.stack.frames().last_mut().expect("open JSON container") = frame;
283    }
284
285    fn close(&mut self, token: JsonToken<'a>) -> Result<Option<JsonEvent<'a>>, JsonReadError> {
286        let start = self.position;
287        self.advance()?;
288        self.stack.pop();
289        Ok(Some(self.event(start, token)))
290    }
291
292    fn event(&self, start: usize, token: JsonToken<'a>) -> JsonEvent<'a> {
293        JsonEvent {
294            token,
295            range: start..self.position,
296        }
297    }
298
299    fn check(&self) -> Result<(), JsonReadError> {
300        if let Some(cancellation) = self.cancellation {
301            cancellation.check()?;
302        }
303        if let Some(production) = self.production {
304            production.check_cancellation()?;
305        }
306        Ok(())
307    }
308}
309
310#[cfg(test)]
311mod tests;