Skip to main content

microsandbox_control_client/
json_value.rs

1//! Lossless JSON inspection without changing serde_json's global number policy.
2
3use std::collections::BTreeMap;
4use std::fmt;
5
6use serde::Deserializer;
7use serde::de::{MapAccess, Visitor};
8use serde_json::value::RawValue;
9use zeroize::Zeroize;
10
11//--------------------------------------------------------------------------------------------------
12// Types
13//--------------------------------------------------------------------------------------------------
14
15/// An original JSON number token, including its integer precision and spelling.
16#[derive(Clone, PartialEq, Eq)]
17pub struct JsonNumber(String);
18
19/// Inspectable JSON that preserves number tokens and rejects duplicate keys.
20#[derive(Clone, PartialEq, Eq)]
21pub enum JsonValue {
22    /// JSON null.
23    Null,
24    /// JSON boolean.
25    Bool(bool),
26    /// Original, validated numeric token; no float conversion has occurred.
27    Number(JsonNumber),
28    /// Decoded Unicode text.
29    String(String),
30    /// Ordered array entries.
31    Array(Vec<JsonValue>),
32    /// Unique object keys. Original ordering and escapes remain in reply bytes.
33    Object(BTreeMap<String, JsonValue>),
34}
35
36struct ObjectVisitor {
37    depth: usize,
38}
39
40//--------------------------------------------------------------------------------------------------
41// Methods
42//--------------------------------------------------------------------------------------------------
43
44impl JsonNumber {
45    /// Original token, without rounding or reformatting.
46    pub fn as_str(&self) -> &str {
47        &self.0
48    }
49
50    /// Read an unsigned integer token. Fractions and exponents are not integers
51    /// in the control wire contract, even when mathematically integral.
52    pub fn as_u64(&self) -> Option<u64> {
53        if self.0.starts_with('-') {
54            return None;
55        }
56        self.0.parse().ok()
57    }
58}
59
60impl JsonValue {
61    /// Decode exactly one JSON value, preserving unknown full-width numbers.
62    /// Errors contain no payload excerpts or decoded values.
63    pub fn parse(bytes: &[u8]) -> Result<Self, &'static str> {
64        let raw: &RawValue = serde_json::from_slice(bytes).map_err(|_| "invalid JSON")?;
65        parse_value(raw.get(), 0).map_err(|_| "invalid JSON")
66    }
67
68    /// Look up one object field without converting its numeric values.
69    pub fn get(&self, name: &str) -> Option<&Self> {
70        self.as_object()?.get(name)
71    }
72
73    /// Borrow object fields.
74    pub fn as_object(&self) -> Option<&BTreeMap<String, Self>> {
75        match self {
76            Self::Object(fields) => Some(fields),
77            _ => None,
78        }
79    }
80
81    /// Borrow ordered array entries.
82    pub fn as_array(&self) -> Option<&[Self]> {
83        match self {
84            Self::Array(values) => Some(values),
85            _ => None,
86        }
87    }
88
89    /// Borrow decoded text.
90    pub fn as_str(&self) -> Option<&str> {
91        match self {
92            Self::String(value) => Some(value),
93            _ => None,
94        }
95    }
96
97    /// Read a boolean without coercion.
98    pub fn as_bool(&self) -> Option<bool> {
99        match self {
100            Self::Bool(value) => Some(*value),
101            _ => None,
102        }
103    }
104
105    /// Read a u64 integer token without a floating-point intermediate.
106    pub fn as_u64(&self) -> Option<u64> {
107        match self {
108            Self::Number(value) => value.as_u64(),
109            _ => None,
110        }
111    }
112}
113
114//--------------------------------------------------------------------------------------------------
115// Trait Implementations
116//--------------------------------------------------------------------------------------------------
117
118impl<'de> Visitor<'de> for ObjectVisitor {
119    type Value = JsonValue;
120
121    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
122        formatter.write_str("a JSON object")
123    }
124
125    fn visit_map<M: MapAccess<'de>>(self, mut access: M) -> Result<Self::Value, M::Error> {
126        let mut fields = BTreeMap::new();
127        while let Some(name) = access.next_key::<String>()? {
128            // Checking before insertion also rejects differently escaped keys
129            // that decode to the same name, including unknown extension fields.
130            if fields.contains_key(&name) {
131                return Err(serde::de::Error::custom("duplicate JSON field"));
132            }
133            let raw: &'de RawValue = access.next_value()?;
134            let value = parse_value(raw.get(), self.depth + 1)
135                .map_err(|_| serde::de::Error::custom("invalid JSON field"))?;
136            fields.insert(name, value);
137        }
138        Ok(JsonValue::Object(fields))
139    }
140}
141
142impl fmt::Debug for JsonValue {
143    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
144        // JSON replies may contain arbitrary peer extensions. Inspection is
145        // explicit; routine error logging must not expose their contents.
146        formatter.write_str("JsonValue { .. }")
147    }
148}
149
150impl fmt::Debug for JsonNumber {
151    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
152        formatter.write_str("JsonNumber { .. }")
153    }
154}
155
156impl Drop for JsonValue {
157    fn drop(&mut self) {
158        match self {
159            Self::String(text) => text.zeroize(),
160            Self::Number(number) => number.0.zeroize(),
161            _ => {} // Nested values run this same drop when their owner drops.
162        }
163    }
164}
165
166//--------------------------------------------------------------------------------------------------
167// Functions
168//--------------------------------------------------------------------------------------------------
169
170fn parse_value(raw: &str, depth: usize) -> Result<JsonValue, serde_json::Error> {
171    if depth > 128 {
172        return Err(<serde_json::Error as serde::de::Error>::custom(
173            "JSON nesting limit",
174        ));
175    }
176    Ok(match raw.as_bytes().first() {
177        Some(b'{') => {
178            let mut decoder = serde_json::Deserializer::from_str(raw);
179            let value = decoder.deserialize_map(ObjectVisitor { depth })?;
180            decoder.end()?;
181            value
182        }
183        Some(b'[') => {
184            let entries: Vec<&RawValue> = serde_json::from_str(raw)?;
185            JsonValue::Array(
186                entries
187                    .iter()
188                    .map(|entry| parse_value(entry.get(), depth + 1))
189                    .collect::<Result<_, _>>()?,
190            )
191        }
192        Some(b'"') => JsonValue::String(serde_json::from_str(raw)?),
193        Some(b't' | b'f') => JsonValue::Bool(serde_json::from_str(raw)?),
194        Some(b'n') => JsonValue::Null,
195        _ => JsonValue::Number(JsonNumber(raw.to_owned())),
196    })
197}