Skip to main content

ledgence_worker_api/
json.rs

1use crate::{Error, ErrorKind, Result};
2use serde::de::DeserializeOwned;
3
4/// Decode JSON without silently rounding integer tokens outside i64/u64.
5///
6/// Use this at transport boundaries before constructing portable contract types.
7/// Fractions and exponent-form numbers use finite binary64, as in the Python
8/// helper; encode arbitrary-precision decimals or larger integers as strings.
9/// JSON syntax, nesting and the target schema are still checked by serde_json.
10pub fn decode_json<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
11    // Only identify unquoted numeric tokens here; do not implement JSON syntax
12    // or interpret strings. serde_json performs the complete parse below.
13    let mut index = 0;
14    let mut quoted = false;
15    while index < bytes.len() {
16        match bytes[index] {
17            b'\\' if quoted => {
18                index += 2;
19                continue;
20            }
21            b'"' => quoted = !quoted,
22            b'-' | b'0'..=b'9' if !quoted => {
23                let start = index;
24                while index < bytes.len()
25                    && matches!(bytes[index], b'0'..=b'9' | b'-' | b'+' | b'.' | b'e' | b'E')
26                {
27                    index += 1;
28                }
29                let token = &bytes[start..index];
30                if !token.iter().any(|byte| matches!(byte, b'.' | b'e' | b'E')) {
31                    let token = std::str::from_utf8(token).expect("numeric token is ASCII");
32                    let representable = if token.starts_with('-') {
33                        token.parse::<i64>().is_ok()
34                    } else {
35                        token.parse::<u64>().is_ok()
36                    };
37                    if !representable {
38                        return Err(Error::new(
39                            ErrorKind::InvalidInput,
40                            "JSON integer exceeds the signed/unsigned 64-bit range; encode exact larger values as strings",
41                        ));
42                    }
43                }
44                continue;
45            }
46            _ => {}
47        }
48        index += 1;
49    }
50    serde_json::from_slice(bytes)
51        .map_err(|error| Error::new(ErrorKind::InvalidInput, format!("invalid JSON: {error}")))
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use serde_json::{Value, json};
58
59    #[test]
60    fn integer_limits_are_checked_before_lossy_value_construction() {
61        for (text, expected) in [
62            ("-9223372036854775808", json!(i64::MIN)),
63            ("18446744073709551615", json!(u64::MAX)),
64            ("9007199254740993", json!(9_007_199_254_740_993_u64)),
65        ] {
66            assert_eq!(decode_json::<Value>(text.as_bytes()).unwrap(), expected);
67        }
68        for text in [
69            "-9223372036854775809",
70            "18446744073709551616",
71            "1000000000000000000000000000000000000000",
72        ] {
73            assert!(decode_json::<Value>(text.as_bytes()).is_err(), "{text}");
74            assert!(decode_json::<Value>(format!("{{\"data\":[{text}]}}").as_bytes()).is_err());
75        }
76    }
77
78    #[test]
79    fn strings_and_application_keys_are_opaque_and_syntax_still_validates() {
80        let original = json!({"$serde_json::private::Number": "18446744073709551616", "18446744073709551616": "escaped \\\" quote and digits 18446744073709551616", "data":[null,true,2.5,1e100]});
81        assert_eq!(
82            decode_json::<Value>(&serde_json::to_vec(&original).unwrap()).unwrap(),
83            original
84        );
85        for invalid in ["[1 2]", "01", "--1", "1e", "NaN", "1e400", "\"unterminated"] {
86            assert!(
87                decode_json::<Value>(invalid.as_bytes()).is_err(),
88                "{invalid}"
89            );
90        }
91    }
92    #[test]
93    fn binary64_tokens_preserve_the_nearest_representable_value() {
94        let value =
95            decode_json::<Value>(b"[2.291712365432881e-09,-1.527077339613215e-236]").unwrap();
96        let numbers = value.as_array().unwrap();
97        assert_eq!(numbers.len(), 2);
98        for (actual, expected) in numbers
99            .iter()
100            .zip([2.291712365432881e-09_f64, -1.527077339613215e-236_f64])
101        {
102            assert_eq!(actual.as_f64().unwrap().to_bits(), expected.to_bits());
103        }
104        assert_eq!(
105            decode_json::<Value>(&serde_json::to_vec(&value).unwrap()).unwrap(),
106            value,
107        );
108    }
109}