1use std::fmt::Write as _;
6
7use crate::utils::sign::msgpack::{Map, Value};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum Json {
11 Null,
12 Bool(bool),
13 Number(String),
15 String(String),
16 Array(Vec<Json>),
17 Object(Vec<(String, Json)>),
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
23#[error("line {line}, column {column}: {message}")]
24pub struct BadJson {
25 pub line: usize,
26 pub column: usize,
27 pub message: String,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
32pub enum Unpackable {
33 #[error("{0} is not a whole number; the venue hashes decimals as strings, so write it in quotes")]
34 Fraction(String),
35 #[error("{0} does not fit in 64 bits")]
36 TooWide(String),
37 #[error("an action is an object, and this is {0}")]
38 NotAnObject(&'static str),
39}
40
41impl Json {
42 pub fn parse(text: &str) -> Result<Json, BadJson> {
43 let mut reader = Reader { text: text.as_bytes(), at: 0, source: text };
44 reader.space();
45 let value = reader.value(0)?;
46 reader.space();
47 if reader.at < reader.text.len() {
48 return Err(reader.fail("more after the value ends"));
49 }
50 Ok(value)
51 }
52
53 #[must_use]
54 pub fn get(&self, key: &str) -> Option<&Json> {
55 match self {
56 Json::Object(entries) => entries.iter().find(|(name, _)| name == key).map(|(_, value)| value),
57 _ => None,
58 }
59 }
60
61 #[must_use]
62 pub fn as_str(&self) -> Option<&str> {
63 match self {
64 Json::String(text) => Some(text),
65 _ => None,
66 }
67 }
68
69 #[must_use]
71 pub const fn as_bool(&self) -> Option<bool> {
72 match self {
73 Json::Bool(value) => Some(*value),
74 _ => None,
75 }
76 }
77
78 #[must_use]
80 pub fn as_u64(&self) -> Option<u64> {
81 match self {
82 Json::Number(digits) => digits.parse().ok(),
83 _ => None,
84 }
85 }
86
87 #[must_use]
88 pub fn as_array(&self) -> Option<&[Json]> {
89 match self {
90 Json::Array(items) => Some(items),
91 _ => None,
92 }
93 }
94
95 #[must_use]
96 pub fn as_object(&self) -> Option<&[(String, Json)]> {
97 match self {
98 Json::Object(entries) => Some(entries),
99 _ => None,
100 }
101 }
102
103 #[must_use]
104 pub const fn kind(&self) -> &'static str {
105 match self {
106 Json::Null => "null",
107 Json::Bool(_) => "a boolean",
108 Json::Number(_) => "a number",
109 Json::String(_) => "a string",
110 Json::Array(_) => "an array",
111 Json::Object(_) => "an object",
112 }
113 }
114
115 #[must_use]
117 pub fn compact(&self) -> String {
118 let mut out = String::new();
119 self.write(&mut out, None, 0);
120 out
121 }
122
123 #[must_use]
125 pub fn pretty(&self) -> String {
126 let mut out = String::new();
127 self.write(&mut out, Some(2), 0);
128 out
129 }
130
131 fn write(&self, out: &mut String, indent: Option<usize>, depth: usize) {
132 let open = |out: &mut String, depth: usize| {
133 if let Some(step) = indent {
134 out.push('\n');
135 out.push_str(&" ".repeat(step * depth));
136 }
137 };
138 match self {
139 Json::Null => out.push_str("null"),
140 Json::Bool(value) => out.push_str(if *value { "true" } else { "false" }),
141 Json::Number(digits) => out.push_str(digits),
142 Json::String(text) => quote(text, out),
143 Json::Array(items) if items.is_empty() => out.push_str("[]"),
144 Json::Array(items) => {
145 out.push('[');
146 for (at, item) in items.iter().enumerate() {
147 if at > 0 {
148 out.push(',');
149 }
150 open(out, depth + 1);
151 item.write(out, indent, depth + 1);
152 }
153 open(out, depth);
154 out.push(']');
155 }
156 Json::Object(entries) if entries.is_empty() => out.push_str("{}"),
157 Json::Object(entries) => {
158 out.push('{');
159 for (at, (key, value)) in entries.iter().enumerate() {
160 if at > 0 {
161 out.push(',');
162 }
163 open(out, depth + 1);
164 quote(key, out);
165 out.push(':');
166 if indent.is_some() {
167 out.push(' ');
168 }
169 value.write(out, indent, depth + 1);
170 }
171 open(out, depth);
172 out.push('}');
173 }
174 }
175 }
176
177 pub fn to_msgpack(&self) -> Result<Value, Unpackable> {
179 Ok(match self {
180 Json::Null => Value::Nil,
181 Json::Bool(value) => Value::Bool(*value),
182 Json::Number(digits) => whole(digits)?,
183 Json::String(text) => Value::from(text.clone()),
184 Json::Array(items) => Value::Array(items.iter().map(Json::to_msgpack).collect::<Result<_, _>>()?),
185 Json::Object(entries) => Value::Map(map_of(entries)?),
186 })
187 }
188
189 pub fn to_action(&self) -> Result<Map, Unpackable> {
191 match self {
192 Json::Object(entries) => map_of(entries),
193 other => Err(Unpackable::NotAnObject(other.kind())),
194 }
195 }
196}
197
198fn map_of(entries: &[(String, Json)]) -> Result<Map, Unpackable> {
199 let mut map = Map::new();
200 for (key, value) in entries {
201 map = map.put(key.clone(), value.to_msgpack()?);
202 }
203 Ok(map)
204}
205
206fn whole(digits: &str) -> Result<Value, Unpackable> {
210 if digits.contains(['.', 'e', 'E']) {
211 return Err(Unpackable::Fraction(digits.to_owned()));
212 }
213 match digits.strip_prefix('-') {
214 Some(_) => digits.parse::<i64>().map(Value::Int).map_err(|_| Unpackable::TooWide(digits.to_owned())),
215 None => digits.parse::<u64>().map(Value::Uint).map_err(|_| Unpackable::TooWide(digits.to_owned())),
216 }
217}
218
219fn quote(text: &str, out: &mut String) {
220 out.push('"');
221 for letter in text.chars() {
222 match letter {
223 '"' => out.push_str("\\\""),
224 '\\' => out.push_str("\\\\"),
225 '\n' => out.push_str("\\n"),
226 '\r' => out.push_str("\\r"),
227 '\t' => out.push_str("\\t"),
228 '\u{08}' => out.push_str("\\b"),
229 '\u{0c}' => out.push_str("\\f"),
230 control if u32::from(control) < 0x20 => {
231 let _ = write!(out, "\\u{:04x}", u32::from(control));
232 }
233 other => out.push(other),
234 }
235 }
236 out.push('"');
237}
238
239const DEEPEST: usize = 128;
241
242struct Reader<'a> {
243 text: &'a [u8],
244 at: usize,
245 source: &'a str,
246}
247
248impl Reader<'_> {
249 fn fail(&self, message: &str) -> BadJson {
250 let before = self.source.get(..self.at).unwrap_or(self.source);
251 let line = before.matches('\n').count() + 1;
252 let column = before.rsplit('\n').next().map_or(0, |tail| tail.chars().count()) + 1;
253 BadJson { line, column, message: message.to_owned() }
254 }
255
256 fn peek(&self) -> Option<u8> {
257 self.text.get(self.at).copied()
258 }
259
260 fn space(&mut self) {
261 while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) {
262 self.at += 1;
263 }
264 }
265
266 fn expect(&mut self, word: &str) -> Result<(), BadJson> {
267 match self.text[self.at..].starts_with(word.as_bytes()) {
268 true => {
269 self.at += word.len();
270 Ok(())
271 }
272 false => Err(self.fail(&format!("expected {word}"))),
273 }
274 }
275
276 fn value(&mut self, depth: usize) -> Result<Json, BadJson> {
277 if depth > DEEPEST {
278 return Err(self.fail("nested too deep"));
279 }
280 match self.peek() {
281 None => Err(self.fail("the text ends where a value should be")),
282 Some(b'n') => self.expect("null").map(|()| Json::Null),
283 Some(b't') => self.expect("true").map(|()| Json::Bool(true)),
284 Some(b'f') => self.expect("false").map(|()| Json::Bool(false)),
285 Some(b'"') => self.string().map(Json::String),
286 Some(b'[') => self.array(depth),
287 Some(b'{') => self.object(depth),
288 Some(b'-' | b'0'..=b'9') => self.number(),
289 Some(_) => Err(self.fail("not the start of a value")),
290 }
291 }
292
293 fn array(&mut self, depth: usize) -> Result<Json, BadJson> {
294 self.at += 1;
295 let mut items = Vec::new();
296 self.space();
297 if self.peek() == Some(b']') {
298 self.at += 1;
299 return Ok(Json::Array(items));
300 }
301 loop {
302 self.space();
303 items.push(self.value(depth + 1)?);
304 self.space();
305 match self.peek() {
306 Some(b',') => self.at += 1,
307 Some(b']') => {
308 self.at += 1;
309 return Ok(Json::Array(items));
310 }
311 _ => return Err(self.fail("expected , or ] in an array")),
312 }
313 }
314 }
315
316 fn object(&mut self, depth: usize) -> Result<Json, BadJson> {
317 self.at += 1;
318 let mut entries: Vec<(String, Json)> = Vec::new();
319 self.space();
320 if self.peek() == Some(b'}') {
321 self.at += 1;
322 return Ok(Json::Object(entries));
323 }
324 loop {
325 self.space();
326 if self.peek() != Some(b'"') {
327 return Err(self.fail("expected a key in quotes"));
328 }
329 let key = self.string()?;
330 if entries.iter().any(|(seen, _)| *seen == key) {
331 return Err(self.fail(&format!("the key {key:?} is written twice")));
332 }
333 self.space();
334 if self.peek() != Some(b':') {
335 return Err(self.fail("expected : after a key"));
336 }
337 self.at += 1;
338 self.space();
339 let value = self.value(depth + 1)?;
340 entries.push((key, value));
341 self.space();
342 match self.peek() {
343 Some(b',') => self.at += 1,
344 Some(b'}') => {
345 self.at += 1;
346 return Ok(Json::Object(entries));
347 }
348 _ => return Err(self.fail("expected , or } in an object")),
349 }
350 }
351 }
352
353 fn number(&mut self) -> Result<Json, BadJson> {
354 let start = self.at;
355 if self.peek() == Some(b'-') {
356 self.at += 1;
357 }
358 let digits = |reader: &mut Self| {
359 let from = reader.at;
360 while matches!(reader.peek(), Some(b'0'..=b'9')) {
361 reader.at += 1;
362 }
363 reader.at - from
364 };
365 let whole = self.at;
366 if digits(self) == 0 {
367 return Err(self.fail("a number needs digits"));
368 }
369 if self.text[whole] == b'0' && self.at - whole > 1 {
370 return Err(self.fail("a number does not start with 0"));
371 }
372 if self.peek() == Some(b'.') {
373 self.at += 1;
374 if digits(self) == 0 {
375 return Err(self.fail("digits must follow the point"));
376 }
377 }
378 if matches!(self.peek(), Some(b'e' | b'E')) {
379 self.at += 1;
380 if matches!(self.peek(), Some(b'+' | b'-')) {
381 self.at += 1;
382 }
383 if digits(self) == 0 {
384 return Err(self.fail("digits must follow the exponent"));
385 }
386 }
387 Ok(Json::Number(self.source[start..self.at].to_owned()))
388 }
389
390 fn string(&mut self) -> Result<String, BadJson> {
391 self.at += 1;
392 let mut out = String::new();
393 loop {
394 let start = self.at;
395 while !matches!(self.peek(), None | Some(b'"' | b'\\')) {
396 if self.text[self.at] < 0x20 {
397 return Err(self.fail("a raw control character inside a string"));
398 }
399 self.at += 1;
400 }
401 out.push_str(&self.source[start..self.at]);
402 match self.peek() {
403 None => return Err(self.fail("a string that never closes")),
404 Some(b'"') => {
405 self.at += 1;
406 return Ok(out);
407 }
408 _ => {
409 self.at += 1;
410 let escaped = self.peek().ok_or_else(|| self.fail("a string that never closes"))?;
411 self.at += 1;
412 match escaped {
413 b'"' => out.push('"'),
414 b'\\' => out.push('\\'),
415 b'/' => out.push('/'),
416 b'b' => out.push('\u{08}'),
417 b'f' => out.push('\u{0c}'),
418 b'n' => out.push('\n'),
419 b'r' => out.push('\r'),
420 b't' => out.push('\t'),
421 b'u' => out.push(self.unicode()?),
422 _ => return Err(self.fail("not an escape JSON knows")),
423 }
424 }
425 }
426 }
427 }
428
429 fn unicode(&mut self) -> Result<char, BadJson> {
430 let first = self.four()?;
431 if (0xd800..0xdc00).contains(&first) {
432 if !self.text[self.at..].starts_with(b"\\u") {
433 return Err(self.fail("half of a surrogate pair"));
434 }
435 self.at += 2;
436 let second = self.four()?;
437 if !(0xdc00..0xe000).contains(&second) {
438 return Err(self.fail("half of a surrogate pair"));
439 }
440 let joined = 0x10000 + ((first - 0xd800) << 10) + (second - 0xdc00);
441 return char::from_u32(joined).ok_or_else(|| self.fail("not a character"));
442 }
443 char::from_u32(first).ok_or_else(|| self.fail("half of a surrogate pair"))
444 }
445
446 fn four(&mut self) -> Result<u32, BadJson> {
447 let digits = self.source.get(self.at..self.at + 4).ok_or_else(|| self.fail("\\u needs four hex digits"))?;
448 let value = u32::from_str_radix(digits, 16).map_err(|_| self.fail("\\u needs four hex digits"))?;
449 self.at += 4;
450 Ok(value)
451 }
452}
453
454#[cfg(test)]
455#[path = "tests/json.rs"]
456mod tests;