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