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