Skip to main content

libc_abi_tools/provider/
json.rs

1//! The JSON subset a provider manifest uses: objects, arrays, strings,
2//! non-negative integers, booleans and null. Std-only, bounded.
3use std::fmt::Write as _;
4
5/// A JSON value of the supported subset.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum Value {
8    /// `null`.
9    Null,
10    /// `true` or `false`.
11    Bool(bool),
12    /// A non-negative integer that fits in `u64`.
13    Integer(u64),
14    /// A string, unescaped.
15    String(String),
16    /// An array.
17    Array(Vec<Value>),
18    /// Members in order; keys are unique.
19    Object(Vec<(String, Value)>),
20}
21
22impl Value {
23    /// The member KEY of an object; `None` if this is not an object or has no
24    /// such member.
25    pub fn get(&self, key: &str) -> Option<&Value> {
26        match self {
27            Value::Object(members) => members.iter().find(|(k, _)| k == key).map(|(_, v)| v),
28            _ => None,
29        }
30    }
31
32    /// The string member KEY of an object.
33    ///
34    /// # Errors
35    ///
36    /// When there is no such member or it is not a string.
37    pub fn str(&self, key: &str) -> Result<&str, String> {
38        match self.get(key) {
39            Some(Value::String(s)) => Ok(s),
40            _ => Err(format!("manifest lacks the string {key:?}")),
41        }
42    }
43
44    /// Pretty-printed with two-space indentation and a final newline.
45    pub fn to_text(&self) -> String {
46        let mut out = String::new();
47        self.write(&mut out, 0);
48        out.push('\n');
49        out
50    }
51
52    fn write(&self, out: &mut String, depth: usize) {
53        let indent = |out: &mut String, depth: usize| out.push_str(&"  ".repeat(depth));
54        match self {
55            Value::Null => out.push_str("null"),
56            Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
57            Value::Integer(n) => write!(out, "{n}").expect("String write"),
58            Value::String(s) => quote(out, s),
59            Value::Array(items) if items.is_empty() => out.push_str("[]"),
60            Value::Object(members) if members.is_empty() => out.push_str("{}"),
61            Value::Array(items) => {
62                out.push_str("[\n");
63                for (i, item) in items.iter().enumerate() {
64                    indent(out, depth + 1);
65                    item.write(out, depth + 1);
66                    out.push_str(if i + 1 < items.len() { ",\n" } else { "\n" });
67                }
68                indent(out, depth);
69                out.push(']');
70            }
71            Value::Object(members) => {
72                out.push_str("{\n");
73                for (i, (key, value)) in members.iter().enumerate() {
74                    indent(out, depth + 1);
75                    quote(out, key);
76                    out.push_str(": ");
77                    value.write(out, depth + 1);
78                    out.push_str(if i + 1 < members.len() { ",\n" } else { "\n" });
79                }
80                indent(out, depth);
81                out.push('}');
82            }
83        }
84    }
85}
86
87fn quote(out: &mut String, text: &str) {
88    out.push('"');
89    for c in text.chars() {
90        match c {
91            '"' => out.push_str("\\\""),
92            '\\' => out.push_str("\\\\"),
93            '\n' => out.push_str("\\n"),
94            '\t' => out.push_str("\\t"),
95            '\r' => out.push_str("\\r"),
96            c if (c as u32) < 0x20 => write!(out, "\\u{:04x}", c as u32).expect("String write"),
97            c => out.push(c),
98        }
99    }
100    out.push('"');
101}
102
103/// Parses one JSON value; at most 64 levels deep.
104pub fn parse(text: &str) -> Result<Value, String> {
105    let mut parser = Parser {
106        bytes: text.as_bytes(),
107        at: 0,
108    };
109    let value = parser.value(0)?;
110    parser.space();
111    if parser.at != parser.bytes.len() {
112        return Err("trailing characters after JSON value".into());
113    }
114    Ok(value)
115}
116
117struct Parser<'a> {
118    bytes: &'a [u8],
119    at: usize,
120}
121
122impl Parser<'_> {
123    fn space(&mut self) {
124        while self
125            .bytes
126            .get(self.at)
127            .is_some_and(|b| matches!(b, b' ' | b'\n' | b'\r' | b'\t'))
128        {
129            self.at += 1;
130        }
131    }
132
133    fn expect(&mut self, byte: u8) -> Result<(), String> {
134        self.space();
135        if self.bytes.get(self.at) != Some(&byte) {
136            return Err(format!(
137                "expected {:?} at byte {} of the JSON",
138                byte as char, self.at
139            ));
140        }
141        self.at += 1;
142        Ok(())
143    }
144
145    fn literal(&mut self, word: &str, value: Value) -> Result<Value, String> {
146        if self.bytes[self.at..].starts_with(word.as_bytes()) {
147            self.at += word.len();
148            Ok(value)
149        } else {
150            Err(format!("invalid JSON at byte {}", self.at))
151        }
152    }
153
154    fn value(&mut self, depth: usize) -> Result<Value, String> {
155        if depth > 64 {
156            return Err("JSON nested too deeply".into());
157        }
158        self.space();
159        match self.bytes.get(self.at) {
160            Some(b'{') => {
161                self.at += 1;
162                let mut members: Vec<(String, Value)> = Vec::new();
163                self.space();
164                if self.bytes.get(self.at) == Some(&b'}') {
165                    self.at += 1;
166                    return Ok(Value::Object(members));
167                }
168                loop {
169                    self.space();
170                    let key = self.string()?;
171                    if members.iter().any(|(k, _)| *k == key) {
172                        return Err(format!("duplicate JSON key {key:?}"));
173                    }
174                    self.expect(b':')?;
175                    members.push((key, self.value(depth + 1)?));
176                    self.space();
177                    match self.bytes.get(self.at) {
178                        Some(b',') => self.at += 1,
179                        Some(b'}') => {
180                            self.at += 1;
181                            return Ok(Value::Object(members));
182                        }
183                        _ => return Err(format!("invalid JSON object at byte {}", self.at)),
184                    }
185                }
186            }
187            Some(b'[') => {
188                self.at += 1;
189                let mut items = Vec::new();
190                self.space();
191                if self.bytes.get(self.at) == Some(&b']') {
192                    self.at += 1;
193                    return Ok(Value::Array(items));
194                }
195                loop {
196                    items.push(self.value(depth + 1)?);
197                    self.space();
198                    match self.bytes.get(self.at) {
199                        Some(b',') => self.at += 1,
200                        Some(b']') => {
201                            self.at += 1;
202                            return Ok(Value::Array(items));
203                        }
204                        _ => return Err(format!("invalid JSON array at byte {}", self.at)),
205                    }
206                }
207            }
208            Some(b'"') => Ok(Value::String(self.string()?)),
209            Some(b't') => self.literal("true", Value::Bool(true)),
210            Some(b'f') => self.literal("false", Value::Bool(false)),
211            Some(b'n') => self.literal("null", Value::Null),
212            Some(b'0'..=b'9') => {
213                let start = self.at;
214                while self.bytes.get(self.at).is_some_and(u8::is_ascii_digit) {
215                    self.at += 1;
216                }
217                let text = std::str::from_utf8(&self.bytes[start..self.at]).expect("ASCII");
218                if text.len() > 1 && text.starts_with('0') {
219                    return Err("JSON numbers have no leading zeros".into());
220                }
221                text.parse()
222                    .map(Value::Integer)
223                    .map_err(|_| "JSON integer out of range".into())
224            }
225            _ => Err(format!("invalid JSON at byte {}", self.at)),
226        }
227    }
228
229    fn string(&mut self) -> Result<String, String> {
230        if self.bytes.get(self.at) != Some(&b'"') {
231            return Err(format!("expected a JSON string at byte {}", self.at));
232        }
233        self.at += 1;
234        let mut out = String::new();
235        loop {
236            let rest = std::str::from_utf8(&self.bytes[self.at..])
237                .map_err(|_| "JSON is not UTF-8".to_string())?;
238            let mut chars = rest.chars();
239            let c = chars.next().ok_or("unterminated JSON string")?;
240            self.at += c.len_utf8();
241            match c {
242                '"' => return Ok(out),
243                '\\' => {
244                    let escape = chars.next().ok_or("unterminated JSON escape")?;
245                    self.at += 1;
246                    out.push(match escape {
247                        '"' => '"',
248                        '\\' => '\\',
249                        '/' => '/',
250                        'n' => '\n',
251                        't' => '\t',
252                        'r' => '\r',
253                        'b' => '\u{8}',
254                        'f' => '\u{c}',
255                        'u' => {
256                            let hex = rest.get(2..6).ok_or("short JSON \\u escape")?;
257                            self.at += 4;
258                            u32::from_str_radix(hex, 16)
259                                .ok()
260                                .and_then(char::from_u32)
261                                .ok_or("unsupported JSON \\u escape")?
262                        }
263                        _ => return Err("invalid JSON escape".into()),
264                    });
265                }
266                c if (c as u32) < 0x20 => return Err("control character in JSON string".into()),
267                c => out.push(c),
268            }
269        }
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn round_trips_manifests() {
279        let value = Value::Object(vec![
280            ("mode".into(), Value::String("source \"x\"\n\\ é".into())),
281            ("size".into(), Value::Integer(1234)),
282            (
283                "files".into(),
284                Value::Array(vec![
285                    Value::Object(vec![("ok".into(), Value::Bool(true))]),
286                    Value::Null,
287                    Value::Array(vec![]),
288                ]),
289            ),
290        ]);
291        let text = value.to_text();
292        assert_eq!(parse(&text).unwrap(), value);
293        assert_eq!(value.str("mode").unwrap(), "source \"x\"\n\\ é");
294        assert!(value.str("size").is_err());
295    }
296
297    #[test]
298    fn rejects_invalid_json() {
299        for bad in [
300            "",
301            "{",
302            "{\"a\":1,}",
303            "{\"a\":1,\"a\":2}",
304            "[1 2]",
305            "01",
306            "-1",
307            "1.5",
308            "\"\u{1}\"",
309            "\"\\x\"",
310            "nul",
311            "{} x",
312            &"[".repeat(100),
313        ] {
314            assert!(parse(bad).is_err(), "{bad:?}");
315        }
316    }
317}