1use std::fmt::Write as _;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum Value {
8 Null,
10 Bool(bool),
12 Integer(u64),
14 String(String),
16 Array(Vec<Value>),
18 Object(Vec<(String, Value)>),
20}
21
22impl Value {
23 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 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 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
103pub 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}