Skip to main content

aver/replay/
json.rs

1use std::collections::{BTreeMap, HashMap};
2use std::fmt::Write as _;
3
4use crate::value::{Value, aver_repr, list_slice};
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum JsonValue {
8    Null,
9    Bool(bool),
10    Int(i64),
11    Float(f64),
12    String(String),
13    Array(Vec<JsonValue>),
14    Object(BTreeMap<String, JsonValue>),
15}
16
17pub fn parse_json(input: &str) -> Result<JsonValue, String> {
18    JsonParser::new(input).parse()
19}
20
21pub fn json_to_string(value: &JsonValue) -> String {
22    let mut out = String::new();
23    write_json_compact(&mut out, value);
24    out
25}
26
27pub fn format_json(value: &JsonValue) -> String {
28    let mut out = String::new();
29    write_json_pretty(&mut out, value, 0);
30    out
31}
32
33fn get_required<'a>(
34    obj: &'a BTreeMap<String, JsonValue>,
35    key: &str,
36    path: &str,
37) -> Result<&'a JsonValue, String> {
38    obj.get(key)
39        .ok_or_else(|| format!("{}: missing required field '{}'", path, key))
40}
41
42fn expect_object<'a>(
43    value: &'a JsonValue,
44    path: &str,
45) -> Result<&'a BTreeMap<String, JsonValue>, String> {
46    match value {
47        JsonValue::Object(obj) => Ok(obj),
48        _ => Err(format!("{} must be an object", path)),
49    }
50}
51
52fn parse_array<'a>(value: &'a JsonValue, path: &str) -> Result<&'a Vec<JsonValue>, String> {
53    match value {
54        JsonValue::Array(arr) => Ok(arr),
55        _ => Err(format!("{} must be an array", path)),
56    }
57}
58
59fn parse_string<'a>(value: &'a JsonValue, path: &str) -> Result<&'a str, String> {
60    match value {
61        JsonValue::String(s) => Ok(s),
62        _ => Err(format!("{} must be a string", path)),
63    }
64}
65
66pub fn values_to_json(values: &[Value]) -> Result<Vec<JsonValue>, String> {
67    values.iter().map(value_to_json).collect()
68}
69
70pub fn values_to_json_lossy(values: &[Value]) -> Vec<JsonValue> {
71    values.iter().map(value_to_json_lossy).collect()
72}
73
74pub fn json_values_to_values(values: &[JsonValue]) -> Result<Vec<Value>, String> {
75    values.iter().map(json_to_value).collect()
76}
77
78pub fn value_to_json(value: &Value) -> Result<JsonValue, String> {
79    match value {
80        Value::Int(i) => Ok(JsonValue::Int(*i)),
81        Value::Float(f) => {
82            if !f.is_finite() {
83                return Err("cannot serialize non-finite float (NaN/inf)".to_string());
84            }
85            Ok(JsonValue::Float(*f))
86        }
87        Value::Str(s) => Ok(JsonValue::String(s.clone())),
88        Value::Bool(b) => Ok(JsonValue::Bool(*b)),
89        Value::Unit => Ok(JsonValue::Null),
90        Value::Ok(inner) => Ok(wrap_marker("$ok", value_to_json(inner)?)),
91        Value::Err(inner) => Ok(wrap_marker("$err", value_to_json(inner)?)),
92        Value::Some(inner) => Ok(wrap_marker("$some", value_to_json(inner)?)),
93        Value::None => Ok(wrap_marker("$none", JsonValue::Bool(true))),
94        Value::List(_) | Value::ListSlice { .. } => {
95            let items =
96                list_slice(value).ok_or_else(|| "invalid list representation".to_string())?;
97            let mut arr = Vec::with_capacity(items.len());
98            for item in items {
99                arr.push(value_to_json(item)?);
100            }
101            Ok(JsonValue::Array(arr))
102        }
103        Value::Tuple(items) => {
104            let mut arr = Vec::with_capacity(items.len());
105            for item in items {
106                arr.push(value_to_json(item)?);
107            }
108            Ok(wrap_marker("$tuple", JsonValue::Array(arr)))
109        }
110        Value::Map(entries) => {
111            if entries.keys().all(|k| matches!(k, Value::Str(_))) {
112                let mut obj = BTreeMap::new();
113                for (k, v) in entries {
114                    let Value::Str(key) = k else {
115                        unreachable!("checked above");
116                    };
117                    obj.insert(key.clone(), value_to_json(v)?);
118                }
119                Ok(JsonValue::Object(obj))
120            } else {
121                let mut pairs = Vec::with_capacity(entries.len());
122                for (k, v) in entries {
123                    pairs.push(JsonValue::Array(vec![value_to_json(k)?, value_to_json(v)?]));
124                }
125                Ok(wrap_marker("$map", JsonValue::Array(pairs)))
126            }
127        }
128        Value::Record { type_name, fields } => {
129            let mut fields_obj = BTreeMap::new();
130            for (name, field_value) in fields {
131                fields_obj.insert(name.clone(), value_to_json(field_value)?);
132            }
133            let mut payload = BTreeMap::new();
134            payload.insert("type".to_string(), JsonValue::String(type_name.clone()));
135            payload.insert("fields".to_string(), JsonValue::Object(fields_obj));
136            Ok(wrap_marker("$record", JsonValue::Object(payload)))
137        }
138        Value::Variant {
139            type_name,
140            variant,
141            fields,
142        } => {
143            let mut field_vals = Vec::with_capacity(fields.len());
144            for field in fields {
145                field_vals.push(value_to_json(field)?);
146            }
147            let mut payload = BTreeMap::new();
148            payload.insert("type".to_string(), JsonValue::String(type_name.clone()));
149            payload.insert("name".to_string(), JsonValue::String(variant.clone()));
150            payload.insert("fields".to_string(), JsonValue::Array(field_vals));
151            Ok(wrap_marker("$variant", JsonValue::Object(payload)))
152        }
153        Value::Fn { .. } | Value::Builtin(_) | Value::Namespace { .. } => Err(format!(
154            "cannot serialize non-replay-safe value: {}",
155            aver_repr(value)
156        )),
157    }
158}
159
160pub fn json_to_value(json: &JsonValue) -> Result<Value, String> {
161    match json {
162        JsonValue::Null => Ok(Value::Unit),
163        JsonValue::Bool(b) => Ok(Value::Bool(*b)),
164        JsonValue::Int(i) => Ok(Value::Int(*i)),
165        JsonValue::Float(f) => Ok(Value::Float(*f)),
166        JsonValue::String(s) => Ok(Value::Str(s.clone())),
167        JsonValue::Array(items) => {
168            let mut out = Vec::with_capacity(items.len());
169            for item in items {
170                out.push(json_to_value(item)?);
171            }
172            Ok(Value::List(out))
173        }
174        JsonValue::Object(obj) => {
175            if let Some((marker, payload)) = marker_single_key(obj) {
176                return decode_marker(marker, payload);
177            }
178            let mut map = HashMap::with_capacity(obj.len());
179            for (k, v) in obj {
180                map.insert(Value::Str(k.clone()), json_to_value(v)?);
181            }
182            Ok(Value::Map(map))
183        }
184    }
185}
186
187pub fn first_diff_path(expected: &JsonValue, got: &JsonValue) -> Option<String> {
188    first_diff_path_inner(expected, got, "$")
189}
190
191pub fn value_to_json_lossy(value: &Value) -> JsonValue {
192    match value_to_json(value) {
193        Ok(v) => v,
194        Err(_) => {
195            let mut obj = BTreeMap::new();
196            obj.insert("$opaque".to_string(), JsonValue::String(aver_repr(value)));
197            JsonValue::Object(obj)
198        }
199    }
200}
201
202fn wrap_marker(name: &str, value: JsonValue) -> JsonValue {
203    let mut obj = BTreeMap::new();
204    obj.insert(name.to_string(), value);
205    JsonValue::Object(obj)
206}
207
208fn marker_single_key(obj: &BTreeMap<String, JsonValue>) -> Option<(&str, &JsonValue)> {
209    if obj.len() != 1 {
210        return None;
211    }
212    obj.iter().next().map(|(k, v)| (k.as_str(), v))
213}
214
215fn decode_marker(marker: &str, payload: &JsonValue) -> Result<Value, String> {
216    match marker {
217        "$ok" => Ok(Value::Ok(Box::new(json_to_value(payload)?))),
218        "$err" => Ok(Value::Err(Box::new(json_to_value(payload)?))),
219        "$some" => Ok(Value::Some(Box::new(json_to_value(payload)?))),
220        "$none" => Ok(Value::None),
221        "$tuple" => decode_tuple(payload),
222        "$map" => decode_map(payload),
223        "$record" => decode_record(payload),
224        "$variant" => decode_variant(payload),
225        _ => Err(format!("unknown replay marker '{}'", marker)),
226    }
227}
228
229fn decode_tuple(payload: &JsonValue) -> Result<Value, String> {
230    let items = parse_array(payload, "$tuple")?;
231    let mut out = Vec::with_capacity(items.len());
232    for item in items {
233        out.push(json_to_value(item)?);
234    }
235    Ok(Value::Tuple(out))
236}
237
238fn decode_map(payload: &JsonValue) -> Result<Value, String> {
239    let pairs = parse_array(payload, "$map")?;
240    let mut out = HashMap::with_capacity(pairs.len());
241    for (idx, pair_json) in pairs.iter().enumerate() {
242        let pair = parse_array(pair_json, &format!("$map[{}]", idx))?;
243        if pair.len() != 2 {
244            return Err(format!("$map[{}] must be a 2-element array", idx));
245        }
246        let key = json_to_value(&pair[0])?;
247        let value = json_to_value(&pair[1])?;
248        out.insert(key, value);
249    }
250    Ok(Value::Map(out))
251}
252
253fn decode_record(payload: &JsonValue) -> Result<Value, String> {
254    let obj = expect_object(payload, "$record")?;
255    let type_name =
256        parse_string(get_required(obj, "type", "$record")?, "$record.type")?.to_string();
257    let fields_obj = expect_object(get_required(obj, "fields", "$record")?, "$record.fields")?;
258    let mut fields = Vec::with_capacity(fields_obj.len());
259    for (key, field_val) in fields_obj {
260        fields.push((key.clone(), json_to_value(field_val)?));
261    }
262    Ok(Value::Record { type_name, fields })
263}
264
265fn decode_variant(payload: &JsonValue) -> Result<Value, String> {
266    let obj = expect_object(payload, "$variant")?;
267    let type_name =
268        parse_string(get_required(obj, "type", "$variant")?, "$variant.type")?.to_string();
269    let variant =
270        parse_string(get_required(obj, "name", "$variant")?, "$variant.name")?.to_string();
271    let fields_arr = parse_array(get_required(obj, "fields", "$variant")?, "$variant.fields")?;
272    let mut fields = Vec::with_capacity(fields_arr.len());
273    for val in fields_arr {
274        fields.push(json_to_value(val)?);
275    }
276    Ok(Value::Variant {
277        type_name,
278        variant,
279        fields,
280    })
281}
282
283fn first_diff_path_inner(expected: &JsonValue, got: &JsonValue, path: &str) -> Option<String> {
284    match (expected, got) {
285        (JsonValue::Object(a), JsonValue::Object(b)) => {
286            let mut keys = a.keys().chain(b.keys()).cloned().collect::<Vec<_>>();
287            keys.sort();
288            keys.dedup();
289            for key in keys {
290                let next_path = if path == "$" {
291                    format!("$.{}", key)
292                } else {
293                    format!("{}.{}", path, key)
294                };
295                match (a.get(&key), b.get(&key)) {
296                    (Some(av), Some(bv)) => {
297                        if let Some(diff) = first_diff_path_inner(av, bv, &next_path) {
298                            return Some(diff);
299                        }
300                    }
301                    _ => return Some(next_path),
302                }
303            }
304            None
305        }
306        (JsonValue::Array(a), JsonValue::Array(b)) => {
307            if a.len() != b.len() {
308                return Some(format!("{}[len]", path));
309            }
310            for (idx, (av, bv)) in a.iter().zip(b.iter()).enumerate() {
311                let next_path = format!("{}[{}]", path, idx);
312                if let Some(diff) = first_diff_path_inner(av, bv, &next_path) {
313                    return Some(diff);
314                }
315            }
316            None
317        }
318        _ => {
319            if expected == got {
320                None
321            } else {
322                Some(path.to_string())
323            }
324        }
325    }
326}
327
328fn write_json_compact(out: &mut String, value: &JsonValue) {
329    match value {
330        JsonValue::Null => out.push_str("null"),
331        JsonValue::Bool(true) => out.push_str("true"),
332        JsonValue::Bool(false) => out.push_str("false"),
333        JsonValue::Int(i) => {
334            let _ = write!(out, "{}", i);
335        }
336        JsonValue::Float(f) => out.push_str(&format_float(*f)),
337        JsonValue::String(s) => write_json_string(out, s),
338        JsonValue::Array(arr) => {
339            out.push('[');
340            for (idx, item) in arr.iter().enumerate() {
341                if idx > 0 {
342                    out.push(',');
343                }
344                write_json_compact(out, item);
345            }
346            out.push(']');
347        }
348        JsonValue::Object(obj) => {
349            out.push('{');
350            for (idx, (k, v)) in obj.iter().enumerate() {
351                if idx > 0 {
352                    out.push(',');
353                }
354                write_json_string(out, k);
355                out.push(':');
356                write_json_compact(out, v);
357            }
358            out.push('}');
359        }
360    }
361}
362
363fn write_json_pretty(out: &mut String, value: &JsonValue, indent: usize) {
364    match value {
365        JsonValue::Array(arr) => {
366            if arr.is_empty() {
367                out.push_str("[]");
368                return;
369            }
370            out.push_str("[\n");
371            for (idx, item) in arr.iter().enumerate() {
372                push_indent(out, indent + 2);
373                write_json_pretty(out, item, indent + 2);
374                if idx + 1 < arr.len() {
375                    out.push(',');
376                }
377                out.push('\n');
378            }
379            push_indent(out, indent);
380            out.push(']');
381        }
382        JsonValue::Object(obj) => {
383            if obj.is_empty() {
384                out.push_str("{}");
385                return;
386            }
387            out.push_str("{\n");
388            for (idx, (k, v)) in obj.iter().enumerate() {
389                push_indent(out, indent + 2);
390                write_json_string(out, k);
391                out.push_str(": ");
392                write_json_pretty(out, v, indent + 2);
393                if idx + 1 < obj.len() {
394                    out.push(',');
395                }
396                out.push('\n');
397            }
398            push_indent(out, indent);
399            out.push('}');
400        }
401        _ => write_json_compact(out, value),
402    }
403}
404
405fn push_indent(out: &mut String, indent: usize) {
406    for _ in 0..indent {
407        out.push(' ');
408    }
409}
410
411fn write_json_string(out: &mut String, s: &str) {
412    out.push('"');
413    for ch in s.chars() {
414        match ch {
415            '"' => out.push_str("\\\""),
416            '\\' => out.push_str("\\\\"),
417            '\n' => out.push_str("\\n"),
418            '\r' => out.push_str("\\r"),
419            '\t' => out.push_str("\\t"),
420            '\u{08}' => out.push_str("\\b"),
421            '\u{0C}' => out.push_str("\\f"),
422            c if c < '\u{20}' => {
423                let _ = write!(out, "\\u{:04X}", c as u32);
424            }
425            c => out.push(c),
426        }
427    }
428    out.push('"');
429}
430
431fn format_float(f: f64) -> String {
432    let mut s = format!("{}", f);
433    if !s.contains('.') && !s.contains('e') && !s.contains('E') {
434        s.push_str(".0");
435    }
436    s
437}
438
439struct JsonParser<'a> {
440    src: &'a str,
441    bytes: &'a [u8],
442    pos: usize,
443}
444
445impl<'a> JsonParser<'a> {
446    fn new(src: &'a str) -> Self {
447        Self {
448            src,
449            bytes: src.as_bytes(),
450            pos: 0,
451        }
452    }
453
454    fn parse(mut self) -> Result<JsonValue, String> {
455        self.skip_ws();
456        let value = self.parse_value()?;
457        self.skip_ws();
458        if self.pos != self.bytes.len() {
459            return Err(self.error("trailing characters after JSON value"));
460        }
461        Ok(value)
462    }
463
464    fn parse_value(&mut self) -> Result<JsonValue, String> {
465        self.skip_ws();
466        let Some(byte) = self.peek() else {
467            return Err(self.error("unexpected end of input"));
468        };
469
470        match byte {
471            b'n' => {
472                self.expect_keyword("null")?;
473                Ok(JsonValue::Null)
474            }
475            b't' => {
476                self.expect_keyword("true")?;
477                Ok(JsonValue::Bool(true))
478            }
479            b'f' => {
480                self.expect_keyword("false")?;
481                Ok(JsonValue::Bool(false))
482            }
483            b'"' => Ok(JsonValue::String(self.parse_string()?)),
484            b'[' => self.parse_array(),
485            b'{' => self.parse_object(),
486            b'-' | b'0'..=b'9' => self.parse_number(),
487            _ => Err(self.error("unexpected token")),
488        }
489    }
490
491    fn parse_array(&mut self) -> Result<JsonValue, String> {
492        self.expect_byte(b'[')?;
493        self.skip_ws();
494
495        let mut items = Vec::new();
496        if self.peek() == Some(b']') {
497            self.pos += 1;
498            return Ok(JsonValue::Array(items));
499        }
500
501        loop {
502            items.push(self.parse_value()?);
503            self.skip_ws();
504            match self.peek() {
505                Some(b',') => {
506                    self.pos += 1;
507                    self.skip_ws();
508                }
509                Some(b']') => {
510                    self.pos += 1;
511                    break;
512                }
513                _ => return Err(self.error("expected ',' or ']' in array")),
514            }
515        }
516
517        Ok(JsonValue::Array(items))
518    }
519
520    fn parse_object(&mut self) -> Result<JsonValue, String> {
521        self.expect_byte(b'{')?;
522        self.skip_ws();
523
524        let mut fields = BTreeMap::new();
525        if self.peek() == Some(b'}') {
526            self.pos += 1;
527            return Ok(JsonValue::Object(fields));
528        }
529
530        loop {
531            let key = self.parse_string()?;
532            self.skip_ws();
533            self.expect_byte(b':')?;
534            self.skip_ws();
535            let value = self.parse_value()?;
536            fields.insert(key, value);
537            self.skip_ws();
538
539            match self.peek() {
540                Some(b',') => {
541                    self.pos += 1;
542                    self.skip_ws();
543                }
544                Some(b'}') => {
545                    self.pos += 1;
546                    break;
547                }
548                _ => return Err(self.error("expected ',' or '}' in object")),
549            }
550        }
551
552        Ok(JsonValue::Object(fields))
553    }
554
555    fn parse_string(&mut self) -> Result<String, String> {
556        self.expect_byte(b'"')?;
557        let mut out = String::new();
558        let mut chunk_start = self.pos;
559
560        while self.pos < self.bytes.len() {
561            let b = self.bytes[self.pos];
562            match b {
563                b'"' => {
564                    if chunk_start < self.pos {
565                        out.push_str(
566                            std::str::from_utf8(&self.bytes[chunk_start..self.pos])
567                                .map_err(|_| self.error("invalid UTF-8 in string"))?,
568                        );
569                    }
570                    self.pos += 1;
571                    return Ok(out);
572                }
573                b'\\' => {
574                    if chunk_start < self.pos {
575                        out.push_str(
576                            std::str::from_utf8(&self.bytes[chunk_start..self.pos])
577                                .map_err(|_| self.error("invalid UTF-8 in string"))?,
578                        );
579                    }
580                    self.pos += 1;
581                    out.push(self.parse_escape_sequence()?);
582                    chunk_start = self.pos;
583                }
584                0x00..=0x1F => {
585                    return Err(self.error("control character in string literal"));
586                }
587                _ => {
588                    self.pos += 1;
589                }
590            }
591        }
592
593        Err(self.error("unterminated string literal"))
594    }
595
596    fn parse_escape_sequence(&mut self) -> Result<char, String> {
597        let Some(ch) = self.next_byte() else {
598            return Err(self.error("unterminated escape sequence"));
599        };
600
601        match ch {
602            b'"' => Ok('"'),
603            b'\\' => Ok('\\'),
604            b'/' => Ok('/'),
605            b'b' => Ok('\u{08}'),
606            b'f' => Ok('\u{0C}'),
607            b'n' => Ok('\n'),
608            b'r' => Ok('\r'),
609            b't' => Ok('\t'),
610            b'u' => self.parse_unicode_escape(),
611            _ => Err(self.error("invalid escape sequence")),
612        }
613    }
614
615    fn parse_unicode_escape(&mut self) -> Result<char, String> {
616        let first = self.parse_hex_u16()?;
617
618        if (0xD800..=0xDBFF).contains(&first) {
619            self.expect_byte(b'\\')?;
620            self.expect_byte(b'u')?;
621            let second = self.parse_hex_u16()?;
622            if !(0xDC00..=0xDFFF).contains(&second) {
623                return Err(self.error("invalid low surrogate in unicode escape"));
624            }
625            let high = (first as u32) - 0xD800;
626            let low = (second as u32) - 0xDC00;
627            let codepoint = 0x10000 + ((high << 10) | low);
628            return char::from_u32(codepoint)
629                .ok_or_else(|| self.error("invalid unicode codepoint"));
630        }
631
632        if (0xDC00..=0xDFFF).contains(&first) {
633            return Err(self.error("unexpected low surrogate in unicode escape"));
634        }
635
636        char::from_u32(first as u32).ok_or_else(|| self.error("invalid unicode codepoint"))
637    }
638
639    fn parse_hex_u16(&mut self) -> Result<u16, String> {
640        let mut value: u16 = 0;
641        for _ in 0..4 {
642            let Some(b) = self.next_byte() else {
643                return Err(self.error("incomplete unicode escape"));
644            };
645            value = value
646                .checked_mul(16)
647                .ok_or_else(|| self.error("unicode escape overflow"))?;
648            value = value
649                .checked_add(hex_digit(b).ok_or_else(|| self.error("invalid hex digit"))? as u16)
650                .ok_or_else(|| self.error("unicode escape overflow"))?;
651        }
652        Ok(value)
653    }
654
655    fn parse_number(&mut self) -> Result<JsonValue, String> {
656        let start = self.pos;
657
658        if self.peek() == Some(b'-') {
659            self.pos += 1;
660        }
661
662        match self.peek() {
663            Some(b'0') => {
664                self.pos += 1;
665                if let Some(b'0'..=b'9') = self.peek() {
666                    return Err(self.error("leading zero in number"));
667                }
668            }
669            Some(b'1'..=b'9') => {
670                self.pos += 1;
671                while let Some(b'0'..=b'9') = self.peek() {
672                    self.pos += 1;
673                }
674            }
675            _ => return Err(self.error("invalid number")),
676        }
677
678        let mut is_float = false;
679
680        if self.peek() == Some(b'.') {
681            is_float = true;
682            self.pos += 1;
683            let frac_start = self.pos;
684            while let Some(b'0'..=b'9') = self.peek() {
685                self.pos += 1;
686            }
687            if self.pos == frac_start {
688                return Err(self.error("missing digits after decimal point"));
689            }
690        }
691
692        if matches!(self.peek(), Some(b'e' | b'E')) {
693            is_float = true;
694            self.pos += 1;
695            if matches!(self.peek(), Some(b'+' | b'-')) {
696                self.pos += 1;
697            }
698            let exp_start = self.pos;
699            while let Some(b'0'..=b'9') = self.peek() {
700                self.pos += 1;
701            }
702            if self.pos == exp_start {
703                return Err(self.error("missing exponent digits"));
704            }
705        }
706
707        let number_text = &self.src[start..self.pos];
708        if is_float {
709            let value = number_text
710                .parse::<f64>()
711                .map_err(|_| self.error("invalid floating-point number"))?;
712            if !value.is_finite() {
713                return Err(self.error("non-finite number is not allowed"));
714            }
715            Ok(JsonValue::Float(value))
716        } else {
717            let value = number_text
718                .parse::<i64>()
719                .map_err(|_| self.error("integer out of i64 range"))?;
720            Ok(JsonValue::Int(value))
721        }
722    }
723
724    fn expect_keyword(&mut self, keyword: &str) -> Result<(), String> {
725        let end = self.pos + keyword.len();
726        if end > self.bytes.len() || &self.src[self.pos..end] != keyword {
727            return Err(self.error(&format!("expected '{}'", keyword)));
728        }
729        self.pos = end;
730        Ok(())
731    }
732
733    fn expect_byte(&mut self, expected: u8) -> Result<(), String> {
734        match self.next_byte() {
735            Some(b) if b == expected => Ok(()),
736            _ => Err(self.error(&format!("expected '{}'", expected as char))),
737        }
738    }
739
740    fn peek(&self) -> Option<u8> {
741        self.bytes.get(self.pos).copied()
742    }
743
744    fn next_byte(&mut self) -> Option<u8> {
745        let b = self.peek()?;
746        self.pos += 1;
747        Some(b)
748    }
749
750    fn skip_ws(&mut self) {
751        while let Some(b) = self.peek() {
752            if matches!(b, b' ' | b'\n' | b'\r' | b'\t') {
753                self.pos += 1;
754            } else {
755                break;
756            }
757        }
758    }
759
760    fn error(&self, msg: &str) -> String {
761        format!("JSON parse error at byte {}: {}", self.pos, msg)
762    }
763}
764
765fn hex_digit(byte: u8) -> Option<u8> {
766    match byte {
767        b'0'..=b'9' => Some(byte - b'0'),
768        b'a'..=b'f' => Some(byte - b'a' + 10),
769        b'A'..=b'F' => Some(byte - b'A' + 10),
770        _ => None,
771    }
772}