Skip to main content

agent_core/
json.rs

1// Strict, bounded UTF-8 JSON for local and browser control channels. New shared code.
2
3use alloc::{
4    collections::BTreeSet,
5    format,
6    string::{String, ToString},
7    vec::Vec,
8};
9
10/// Objects with more members than this check duplicate names with an ordered set instead of
11/// comparing every pair (same results, no quadratic time on large objects).
12const SMALL_OBJECT: usize = 16;
13
14/// Exact JSON number token; construction validates the JSON number grammar.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct Number(String);
17
18impl Number {
19    /// Parse one complete JSON number without rounding it through floating point.
20    pub fn parse(text: &str) -> Option<Self> {
21        let mut p = Parser::new(text, Limits::default());
22        let number = p.number().ok()?;
23        (p.pos == text.len()).then_some(number)
24    }
25    /// Original validated JSON representation.
26    pub fn as_str(&self) -> &str {
27        &self.0
28    }
29    /// Read an exactly integral JSON number without precision loss, including exponents.
30    pub fn as_i64(&self) -> Option<i64> {
31        let (negative, magnitude) = self.integer()?;
32        if negative {
33            if magnitude == 1u64 << 63 {
34                Some(i64::MIN)
35            } else {
36                i64::try_from(magnitude).ok().map(|v| -v)
37            }
38        } else {
39            i64::try_from(magnitude).ok()
40        }
41    }
42    /// Read an exactly integral, nonnegative JSON number without precision loss.
43    pub fn as_u64(&self) -> Option<u64> {
44        let (negative, magnitude) = self.integer()?;
45        (!negative || magnitude == 0).then_some(magnitude)
46    }
47    fn integer(&self) -> Option<(bool, u64)> {
48        let negative = self.0.starts_with('-');
49        let text = self.0.strip_prefix('-').unwrap_or(&self.0);
50        let (mantissa, exponent) = if let Some(index) = text.find(['e', 'E']) {
51            let exponent = &text[index + 1..];
52            let exponent_negative = exponent.starts_with('-');
53            let mut magnitude = 0i32;
54            for byte in exponent.bytes().filter(|b| b.is_ascii_digit()) {
55                magnitude = (magnitude.saturating_mul(10) + (byte - b'0') as i32).min(1_000_000);
56            }
57            (
58                &text[..index],
59                if exponent_negative {
60                    -magnitude
61                } else {
62                    magnitude
63                },
64            )
65        } else {
66            (text, 0)
67        };
68        let fraction = mantissa
69            .find('.')
70            .map(|i| mantissa.len() - i - 1)
71            .unwrap_or(0) as i32;
72        let mut digits: Vec<u8> = mantissa
73            .bytes()
74            .filter(|b| *b != b'.')
75            .skip_while(|b| *b == b'0')
76            .collect();
77        if digits.is_empty() {
78            return Some((false, 0));
79        }
80        let mut scale = exponent - fraction;
81        while scale < 0 && digits.last() == Some(&b'0') {
82            digits.pop();
83            scale += 1;
84        }
85        if scale < 0 || scale > 20 || digits.len() + scale as usize > 20 {
86            return None;
87        }
88        let mut magnitude = 0u64;
89        for digit in digits {
90            magnitude = magnitude
91                .checked_mul(10)?
92                .checked_add((digit - b'0') as u64)?;
93        }
94        for _ in 0..scale {
95            magnitude = magnitude.checked_mul(10)?;
96        }
97        Some((negative, magnitude))
98    }
99}
100
101/// Owned JSON data. Objects retain member order and parsed objects have unique keys.
102#[derive(Clone, Debug, PartialEq, Eq)]
103pub enum Value {
104    /// JSON null.
105    Null,
106    /// JSON boolean.
107    Bool(bool),
108    /// Exact JSON number.
109    Number(Number),
110    /// Unicode string.
111    String(String),
112    /// Ordered values.
113    Array(Vec<Value>),
114    /// Ordered name/value members.
115    Object(Vec<(String, Value)>),
116}
117
118impl Value {
119    /// Make a JSON string.
120    pub fn string(value: impl Into<String>) -> Self {
121        Self::String(value.into())
122    }
123    /// Make an unsigned number without converting to floating point.
124    pub fn u64(value: u64) -> Self {
125        Self::Number(Number(value.to_string()))
126    }
127    /// Make a signed number without converting to floating point.
128    pub fn i64(value: i64) -> Self {
129        Self::Number(Number(value.to_string()))
130    }
131    /// Make an object, with later duplicate names replacing earlier values.
132    pub fn object<const N: usize>(fields: [(&str, Value); N]) -> Self {
133        let mut result = Self::Object(Vec::with_capacity(N));
134        for (key, value) in fields {
135            result.insert(key, value);
136        }
137        result
138    }
139    /// Insert or replace an object member; false when this value is not an object.
140    pub fn insert(&mut self, key: &str, value: Value) -> bool {
141        let Self::Object(fields) = self else {
142            return false;
143        };
144        if let Some((_, old)) = fields.iter_mut().find(|(name, _)| name == key) {
145            *old = value;
146        } else {
147            fields.push((key.into(), value));
148        }
149        true
150    }
151    /// Look up an object member.
152    pub fn get(&self, key: &str) -> Option<&Value> {
153        self.as_object()?
154            .iter()
155            .find(|(name, _)| name == key)
156            .map(|(_, value)| value)
157    }
158    /// Borrow an object member list.
159    pub fn as_object(&self) -> Option<&[(String, Value)]> {
160        match self {
161            Self::Object(value) => Some(value),
162            _ => None,
163        }
164    }
165    /// Borrow an array.
166    pub fn as_array(&self) -> Option<&[Value]> {
167        match self {
168            Self::Array(v) => Some(v),
169            _ => None,
170        }
171    }
172    /// Borrow a string.
173    pub fn as_str(&self) -> Option<&str> {
174        match self {
175            Self::String(v) => Some(v),
176            _ => None,
177        }
178    }
179    /// Read a boolean.
180    pub fn as_bool(&self) -> Option<bool> {
181        match self {
182            Self::Bool(v) => Some(*v),
183            _ => None,
184        }
185    }
186    /// Read an unsigned integer token.
187    pub fn as_u64(&self) -> Option<u64> {
188        match self {
189            Self::Number(v) => v.as_u64(),
190            _ => None,
191        }
192    }
193    /// Read a signed integer token.
194    pub fn as_i64(&self) -> Option<i64> {
195        match self {
196            Self::Number(v) => v.as_i64(),
197            _ => None,
198        }
199    }
200}
201
202/// Bounds applied before and during decoding or encoding.
203#[derive(Clone, Copy, Debug)]
204pub struct Limits {
205    /// Maximum encoded byte count.
206    pub bytes: usize,
207    /// Maximum nested arrays/objects; the root value starts at depth zero.
208    pub depth: usize,
209    /// Maximum total values, including the root.
210    pub values: usize,
211    /// Maximum decoded bytes in any string, including member names.
212    pub string_bytes: usize,
213    /// Maximum members/elements in any container.
214    pub members: usize,
215}
216
217impl Default for Limits {
218    fn default() -> Self {
219        Self {
220            bytes: 1_048_576,
221            depth: 32,
222            values: 16_384,
223            string_bytes: 262_144,
224            members: 4096,
225        }
226    }
227}
228
229/// Parse or serialization failure with the byte offset where it was found.
230#[derive(Clone, Debug, PartialEq, Eq)]
231pub struct Error {
232    /// Zero-based byte offset (zero for serialization limits).
233    pub offset: usize,
234    /// Stable machine-readable failure category.
235    pub kind: &'static str,
236}
237
238/// Parse a complete UTF-8 JSON value using default limits.
239pub fn parse(bytes: &[u8]) -> Result<Value, Error> {
240    parse_with_limits(bytes, Limits::default())
241}
242
243/// Parse a complete UTF-8 JSON value. Duplicate object keys and lone surrogates are errors.
244pub fn parse_with_limits(bytes: &[u8], limits: Limits) -> Result<Value, Error> {
245    if bytes.len() > limits.bytes {
246        return Err(Error {
247            offset: limits.bytes,
248            kind: "byte_limit",
249        });
250    }
251    let text = core::str::from_utf8(bytes).map_err(|e| Error {
252        offset: e.valid_up_to(),
253        kind: "invalid_utf8",
254    })?;
255    let mut parser = Parser::new(text, limits);
256    let value = parser.value(0)?;
257    parser.space();
258    if parser.pos != text.len() {
259        return parser.fail("trailing_data");
260    }
261    Ok(value)
262}
263
264struct Parser<'a> {
265    text: &'a str,
266    pos: usize,
267    values: usize,
268    limits: Limits,
269}
270
271impl<'a> Parser<'a> {
272    fn new(text: &'a str, limits: Limits) -> Self {
273        Self {
274            text,
275            pos: 0,
276            values: 0,
277            limits,
278        }
279    }
280    fn fail<T>(&self, kind: &'static str) -> Result<T, Error> {
281        Err(Error {
282            offset: self.pos,
283            kind,
284        })
285    }
286    fn peek(&self) -> Option<u8> {
287        self.text.as_bytes().get(self.pos).copied()
288    }
289    fn space(&mut self) {
290        while matches!(self.peek(), Some(b' ' | b'\r' | b'\n' | b'\t')) {
291            self.pos += 1;
292        }
293    }
294    fn take(&mut self, byte: u8) -> bool {
295        if self.peek() == Some(byte) {
296            self.pos += 1;
297            true
298        } else {
299            false
300        }
301    }
302    fn value(&mut self, depth: usize) -> Result<Value, Error> {
303        self.space();
304        if depth > self.limits.depth {
305            return self.fail("depth_limit");
306        }
307        self.values += 1;
308        if self.values > self.limits.values {
309            return self.fail("value_limit");
310        }
311        match self.peek() {
312            Some(b'n') => {
313                self.literal("null")?;
314                Ok(Value::Null)
315            }
316            Some(b't') => {
317                self.literal("true")?;
318                Ok(Value::Bool(true))
319            }
320            Some(b'f') => {
321                self.literal("false")?;
322                Ok(Value::Bool(false))
323            }
324            Some(b'"') => Ok(Value::String(self.string()?)),
325            Some(b'-' | b'0'..=b'9') => Ok(Value::Number(self.number()?)),
326            Some(b'[') => {
327                self.pos += 1;
328                self.space();
329                let mut values = Vec::new();
330                if self.take(b']') {
331                    return Ok(Value::Array(values));
332                }
333                loop {
334                    if values.len() >= self.limits.members {
335                        return self.fail("member_limit");
336                    }
337                    values.push(self.value(depth + 1)?);
338                    self.space();
339                    if self.take(b']') {
340                        break;
341                    }
342                    if !self.take(b',') {
343                        return self.fail("expected_comma");
344                    }
345                }
346                Ok(Value::Array(values))
347            }
348            Some(b'{') => {
349                self.pos += 1;
350                self.space();
351                let mut fields: Vec<(String, Value)> = Vec::new();
352                let mut names: Option<BTreeSet<String>> = None;
353                if self.take(b'}') {
354                    return Ok(Value::Object(fields));
355                }
356                loop {
357                    if fields.len() >= self.limits.members {
358                        return self.fail("member_limit");
359                    }
360                    self.space();
361                    let key = self.string()?;
362                    self.space();
363                    let duplicate = match &mut names {
364                        Some(set) => !set.insert(key.clone()),
365                        None => fields.iter().any(|(name, _)| name == &key),
366                    };
367                    if duplicate {
368                        return self.fail("duplicate_key");
369                    }
370                    if names.is_none() && fields.len() >= SMALL_OBJECT {
371                        names = Some(fields.iter().map(|(name, _)| name.clone()).chain([key.clone()]).collect());
372                    }
373                    if !self.take(b':') {
374                        return self.fail("expected_colon");
375                    }
376                    fields.push((key, self.value(depth + 1)?));
377                    self.space();
378                    if self.take(b'}') {
379                        break;
380                    }
381                    if !self.take(b',') {
382                        return self.fail("expected_comma");
383                    }
384                }
385                Ok(Value::Object(fields))
386            }
387            _ => self.fail("expected_value"),
388        }
389    }
390    fn literal(&mut self, text: &str) -> Result<(), Error> {
391        if self.text[self.pos..].starts_with(text) {
392            self.pos += text.len();
393            Ok(())
394        } else {
395            self.fail("invalid_literal")
396        }
397    }
398    fn number(&mut self) -> Result<Number, Error> {
399        let start = self.pos;
400        self.take(b'-');
401        if !self.take(b'0') {
402            if !matches!(self.peek(), Some(b'1'..=b'9')) {
403                return self.fail("invalid_number");
404            }
405            while matches!(self.peek(), Some(b'0'..=b'9')) {
406                self.pos += 1;
407            }
408        }
409        if self.take(b'.') {
410            let begin = self.pos;
411            while matches!(self.peek(), Some(b'0'..=b'9')) {
412                self.pos += 1;
413            }
414            if begin == self.pos {
415                return self.fail("invalid_number");
416            }
417        }
418        if self.take(b'e') || self.take(b'E') {
419            if !self.take(b'+') {
420                self.take(b'-');
421            }
422            let begin = self.pos;
423            while matches!(self.peek(), Some(b'0'..=b'9')) {
424                self.pos += 1;
425            }
426            if begin == self.pos {
427                return self.fail("invalid_number");
428            }
429        }
430        if self.pos - start > 256 {
431            return self.fail("number_limit");
432        }
433        Ok(Number(self.text[start..self.pos].into()))
434    }
435    fn hex4(&mut self) -> Result<u32, Error> {
436        let mut result = 0;
437        for _ in 0..4 {
438            let digit = match self.peek() {
439                Some(b'0'..=b'9') => self.peek().unwrap() - b'0',
440                Some(b'a'..=b'f') => self.peek().unwrap() - b'a' + 10,
441                Some(b'A'..=b'F') => self.peek().unwrap() - b'A' + 10,
442                _ => return self.fail("invalid_escape"),
443            };
444            self.pos += 1;
445            result = (result << 4) | digit as u32;
446        }
447        Ok(result)
448    }
449    fn string(&mut self) -> Result<String, Error> {
450        if !self.take(b'"') {
451            return self.fail("expected_string");
452        }
453        let mut result = String::new();
454        loop {
455            match self.peek() {
456                Some(b'"') => {
457                    self.pos += 1;
458                    return Ok(result);
459                }
460                Some(b'\\') => {
461                    self.pos += 1;
462                    let escaped = match self.peek() {
463                        Some(b'"') => '"',
464                        Some(b'\\') => '\\',
465                        Some(b'/') => '/',
466                        Some(b'b') => '\u{8}',
467                        Some(b'f') => '\u{c}',
468                        Some(b'n') => '\n',
469                        Some(b'r') => '\r',
470                        Some(b't') => '\t',
471                        Some(b'u') => {
472                            self.pos += 1;
473                            let first = self.hex4()?;
474                            let scalar = if (0xD800..=0xDBFF).contains(&first) {
475                                if !self.take(b'\\') || !self.take(b'u') {
476                                    return self.fail("unpaired_surrogate");
477                                }
478                                let low = self.hex4()?;
479                                if !(0xDC00..=0xDFFF).contains(&low) {
480                                    return self.fail("unpaired_surrogate");
481                                }
482                                0x10000 + ((first - 0xD800) << 10) + (low - 0xDC00)
483                            } else {
484                                first
485                            };
486                            let Some(ch) = char::from_u32(scalar) else {
487                                return self.fail("unpaired_surrogate");
488                            };
489                            result.push(ch);
490                            if result.len() > self.limits.string_bytes {
491                                return self.fail("string_limit");
492                            }
493                            continue;
494                        }
495                        _ => return self.fail("invalid_escape"),
496                    };
497                    self.pos += 1;
498                    result.push(escaped);
499                }
500                Some(0..=31) => return self.fail("unescaped_control"),
501                Some(_) => {
502                    let Some(ch) = self.text[self.pos..].chars().next() else {
503                        return self.fail("unterminated_string");
504                    };
505                    self.pos += ch.len_utf8();
506                    result.push(ch);
507                }
508                None => return self.fail("unterminated_string"),
509            }
510            if result.len() > self.limits.string_bytes {
511                return self.fail("string_limit");
512            }
513        }
514    }
515}
516
517/// Encode JSON without literal newlines, enforcing the default bounds.
518pub fn stringify(value: &Value) -> Result<String, Error> {
519    stringify_with_limits(value, Limits::default())
520}
521
522/// Encode bounded JSON. Invalid duplicate keys in manually built objects are rejected.
523pub fn stringify_with_limits(value: &Value, limits: Limits) -> Result<String, Error> {
524    let mut writer = Writer {
525        out: String::new(),
526        limits,
527        values: 0,
528        indent: 0,
529    };
530    writer.value(value, 0)?;
531    Ok(writer.out)
532}
533
534/// Encode bounded JSON for people to read: every array element and object member on its own
535/// line, indented by `indent` spaces per level, "name": value; empty containers stay "[]" / "{}".
536/// The output ends without a newline.
537pub fn stringify_pretty(value: &Value, limits: Limits, indent: usize) -> Result<String, Error> {
538    let mut writer = Writer {
539        out: String::new(),
540        limits,
541        values: 0,
542        indent: indent.clamp(1, 8),
543    };
544    writer.value(value, 0)?;
545    Ok(writer.out)
546}
547
548struct Writer {
549    out: String,
550    limits: Limits,
551    values: usize,
552    /// spaces per level; 0 = compact
553    indent: usize,
554}
555
556impl Writer {
557    fn fail<T>(&self, kind: &'static str) -> Result<T, Error> {
558        Err(Error {
559            offset: self.out.len(),
560            kind,
561        })
562    }
563    fn push(&mut self, text: &str) -> Result<(), Error> {
564        if text.len() > self.limits.bytes.saturating_sub(self.out.len()) {
565            return self.fail("byte_limit");
566        }
567        self.out.push_str(text);
568        Ok(())
569    }
570    /// a line break and the indentation of `depth` (pretty output only)
571    fn newline(&mut self, depth: usize) -> Result<(), Error> {
572        if self.indent == 0 {
573            return Ok(());
574        }
575        let n = 1 + depth * self.indent;
576        if n > self.limits.bytes.saturating_sub(self.out.len()) {
577            return self.fail("byte_limit");
578        }
579        self.out.push('\n');
580        for _ in 1..n {
581            self.out.push(' ');
582        }
583        Ok(())
584    }
585    fn string(&mut self, text: &str) -> Result<(), Error> {
586        if text.len() > self.limits.string_bytes {
587            return self.fail("string_limit");
588        }
589        self.push("\"")?;
590        for ch in text.chars() {
591            match ch {
592                '"' => self.push("\\\"")?,
593                '\\' => self.push("\\\\")?,
594                '\n' => self.push("\\n")?,
595                '\r' => self.push("\\r")?,
596                '\t' => self.push("\\t")?,
597                ch if (ch as u32) < 32 => self.push(&format!("\\u{:04x}", ch as u32))?,
598                ch => {
599                    let mut bytes = [0; 4];
600                    self.push(ch.encode_utf8(&mut bytes))?;
601                }
602            }
603        }
604        self.push("\"")
605    }
606    fn value(&mut self, value: &Value, depth: usize) -> Result<(), Error> {
607        if depth > self.limits.depth {
608            return self.fail("depth_limit");
609        }
610        self.values += 1;
611        if self.values > self.limits.values {
612            return self.fail("value_limit");
613        }
614        match value {
615            Value::Null => self.push("null"),
616            Value::Bool(v) => self.push(if *v { "true" } else { "false" }),
617            Value::Number(v) => self.push(v.as_str()),
618            Value::String(v) => self.string(v),
619            Value::Array(values) => {
620                if values.len() > self.limits.members {
621                    return self.fail("member_limit");
622                }
623                self.push("[")?;
624                for (i, item) in values.iter().enumerate() {
625                    if i != 0 {
626                        self.push(",")?;
627                    }
628                    self.newline(depth + 1)?;
629                    self.value(item, depth + 1)?;
630                }
631                if !values.is_empty() {
632                    self.newline(depth)?;
633                }
634                self.push("]")
635            }
636            Value::Object(fields) => {
637                if fields.len() > self.limits.members {
638                    return self.fail("member_limit");
639                }
640                self.push("{")?;
641                let mut names: BTreeSet<&str> = BTreeSet::new();
642                for (i, (name, item)) in fields.iter().enumerate() {
643                    let duplicate = if fields.len() > SMALL_OBJECT {
644                        !names.insert(name)
645                    } else {
646                        fields[..i].iter().any(|(key, _)| key == name)
647                    };
648                    if duplicate {
649                        return self.fail("duplicate_key");
650                    }
651                    if i != 0 {
652                        self.push(",")?;
653                    }
654                    self.newline(depth + 1)?;
655                    self.string(name)?;
656                    self.push(if self.indent > 0 { ": " } else { ":" })?;
657                    self.value(item, depth + 1)?;
658                }
659                if !fields.is_empty() {
660                    self.newline(depth)?;
661                }
662                self.push("}")
663            }
664        }
665    }
666}