daedalus_data/
json.rs

1//! JSON codecs and errors with deterministic representations.
2use base64::Engine;
3use base64::engine::general_purpose::STANDARD as B64;
4use serde_json::Value as JsonValue;
5use std::borrow::Cow;
6
7use crate::errors::{DataError, DataErrorCode, DataResult};
8use crate::model::{EnumValue, StructFieldValue, Value};
9
10/// Encode a `Value` into a deterministic JSON string.
11pub fn to_json(value: &Value) -> DataResult<String> {
12    let json = encode_value(value)?;
13    serde_json::to_string(&json)
14        .map_err(|e| DataError::new(DataErrorCode::Serialization, e.to_string()))
15}
16
17/// Decode a `Value` from a JSON string.
18pub fn from_json(s: &str) -> DataResult<Value> {
19    let parsed: JsonValue = serde_json::from_str(s).map_err(|e| {
20        DataError::new(
21            DataErrorCode::Serialization,
22            format!("parse error at {}: {}", e.line(), e.column()),
23        )
24    })?;
25    decode_value(&parsed)
26}
27
28/// Encode to a structured JSON value.
29pub fn encode_value(value: &Value) -> DataResult<JsonValue> {
30    let mut obj = serde_json::Map::new();
31    match value {
32        Value::Unit => {
33            obj.insert("type".into(), JsonValue::String("unit".into()));
34        }
35        Value::Bool(b) => {
36            obj.insert("type".into(), JsonValue::String("bool".into()));
37            obj.insert("value".into(), JsonValue::Bool(*b));
38        }
39        Value::Int(i) => {
40            obj.insert("type".into(), JsonValue::String("int".into()));
41            obj.insert("value".into(), JsonValue::Number((*i).into()));
42        }
43        Value::Float(f) => {
44            if !f.is_finite() {
45                return Err(DataError::new(
46                    DataErrorCode::Serialization,
47                    "non-finite floats are not supported in JSON",
48                ));
49            }
50            obj.insert("type".into(), JsonValue::String("float".into()));
51            obj.insert(
52                "value".into(),
53                JsonValue::Number(serde_json::Number::from_f64(*f).unwrap()),
54            );
55        }
56        Value::String(s) => {
57            obj.insert("type".into(), JsonValue::String("string".into()));
58            obj.insert("value".into(), JsonValue::String(s.to_string()));
59        }
60        Value::Bytes(bytes) => {
61            obj.insert("type".into(), JsonValue::String("bytes".into()));
62            obj.insert("value".into(), JsonValue::String(B64.encode(bytes)));
63        }
64        Value::List(items) => {
65            obj.insert("type".into(), JsonValue::String("list".into()));
66            let mut arr = Vec::new();
67            for v in items {
68                arr.push(encode_value(v)?);
69            }
70            obj.insert("value".into(), JsonValue::Array(arr));
71        }
72        Value::Map(entries) => {
73            obj.insert("type".into(), JsonValue::String("map".into()));
74            let mut arr = Vec::new();
75            for (k, v) in entries {
76                arr.push(JsonValue::Array(vec![encode_value(k)?, encode_value(v)?]));
77            }
78            obj.insert("value".into(), JsonValue::Array(arr));
79        }
80        Value::Tuple(items) => {
81            obj.insert("type".into(), JsonValue::String("tuple".into()));
82            let mut arr = Vec::new();
83            for v in items {
84                arr.push(encode_value(v)?);
85            }
86            obj.insert("value".into(), JsonValue::Array(arr));
87        }
88        Value::Struct(fields) => {
89            obj.insert("type".into(), JsonValue::String("struct".into()));
90            let mut props = serde_json::Map::new();
91            let mut sorted = fields.clone();
92            sorted.sort_by(|a, b| a.name.cmp(&b.name));
93            for StructFieldValue { name, value } in sorted {
94                props.insert(name, encode_value(&value)?);
95            }
96            obj.insert("value".into(), JsonValue::Object(props));
97        }
98        Value::Enum(ev) => {
99            obj.insert("type".into(), JsonValue::String("enum".into()));
100            let mut inner = serde_json::Map::new();
101            inner.insert("name".into(), JsonValue::String(ev.name.clone()));
102            if let Some(v) = &ev.value {
103                inner.insert("value".into(), encode_value(v)?);
104            }
105            obj.insert("value".into(), JsonValue::Object(inner));
106        }
107    }
108    Ok(JsonValue::Object(obj))
109}
110
111/// Decode from a structured JSON value.
112pub fn decode_value(value: &JsonValue) -> DataResult<Value> {
113    let obj = value.as_object().ok_or_else(|| {
114        DataError::new(
115            DataErrorCode::Serialization,
116            "expected object with fields `type` and optional `value`",
117        )
118    })?;
119    let ty = obj.get("type").and_then(|v| v.as_str()).ok_or_else(|| {
120        DataError::new(
121            DataErrorCode::Serialization,
122            "missing or invalid `type` field",
123        )
124    })?;
125    match ty {
126        "unit" => Ok(Value::Unit),
127        "bool" => obj
128            .get("value")
129            .and_then(|v| v.as_bool())
130            .map(Value::Bool)
131            .ok_or_else(|| {
132                DataError::new(DataErrorCode::Serialization, "expected boolean in `value`")
133            }),
134        "int" => obj
135            .get("value")
136            .and_then(|v| v.as_i64())
137            .map(Value::Int)
138            .ok_or_else(|| {
139                DataError::new(DataErrorCode::Serialization, "expected integer in `value`")
140            }),
141        "float" => {
142            let f = obj.get("value").and_then(|v| v.as_f64()).ok_or_else(|| {
143                DataError::new(DataErrorCode::Serialization, "expected number in `value`")
144            })?;
145            if !f.is_finite() {
146                return Err(DataError::new(
147                    DataErrorCode::Serialization,
148                    "non-finite floats are not supported in JSON",
149                ));
150            }
151            Ok(Value::Float(f))
152        }
153        "string" => obj
154            .get("value")
155            .and_then(|v| v.as_str())
156            .map(|s| Value::String(Cow::Owned(s.to_string())))
157            .ok_or_else(|| {
158                DataError::new(DataErrorCode::Serialization, "expected string in `value`")
159            }),
160        "bytes" => {
161            let s = obj.get("value").and_then(|v| v.as_str()).ok_or_else(|| {
162                DataError::new(
163                    DataErrorCode::Serialization,
164                    "expected base64 string in `value`",
165                )
166            })?;
167            let decoded = B64.decode(s.as_bytes()).map_err(|e| {
168                DataError::new(
169                    DataErrorCode::Serialization,
170                    format!("invalid base64 for bytes: {e}"),
171                )
172            })?;
173            Ok(Value::Bytes(Cow::Owned(decoded)))
174        }
175        "list" => {
176            let arr = obj.get("value").and_then(|v| v.as_array()).ok_or_else(|| {
177                DataError::new(DataErrorCode::Serialization, "expected array in `value`")
178            })?;
179            let mut items = Vec::new();
180            for v in arr {
181                items.push(decode_value(v)?);
182            }
183            Ok(Value::List(items))
184        }
185        "map" => {
186            let arr = obj.get("value").and_then(|v| v.as_array()).ok_or_else(|| {
187                DataError::new(
188                    DataErrorCode::Serialization,
189                    "expected array of pairs in `value`",
190                )
191            })?;
192            let mut entries = Vec::new();
193            for pair in arr {
194                let elems = pair.as_array().ok_or_else(|| {
195                    DataError::new(
196                        DataErrorCode::Serialization,
197                        "expected array pair in map entry",
198                    )
199                })?;
200                if elems.len() != 2 {
201                    return Err(DataError::new(
202                        DataErrorCode::Serialization,
203                        "map entry must have two elements",
204                    ));
205                }
206                let key = decode_value(&elems[0])?;
207                let val = decode_value(&elems[1])?;
208                entries.push((key, val));
209            }
210            Ok(Value::Map(entries))
211        }
212        "tuple" => {
213            let arr = obj.get("value").and_then(|v| v.as_array()).ok_or_else(|| {
214                DataError::new(DataErrorCode::Serialization, "expected array in `value`")
215            })?;
216            let mut items = Vec::new();
217            for v in arr {
218                items.push(decode_value(v)?);
219            }
220            Ok(Value::Tuple(items))
221        }
222        "struct" => {
223            let m = obj
224                .get("value")
225                .and_then(|v| v.as_object())
226                .ok_or_else(|| {
227                    DataError::new(DataErrorCode::Serialization, "expected object in `value`")
228                })?;
229            let mut fields = Vec::new();
230            for (name, val) in m {
231                fields.push(StructFieldValue {
232                    name: name.clone(),
233                    value: decode_value(val)?,
234                });
235            }
236            fields.sort_by(|a, b| a.name.cmp(&b.name));
237            Ok(Value::Struct(fields))
238        }
239        "enum" => {
240            let m = obj
241                .get("value")
242                .and_then(|v| v.as_object())
243                .ok_or_else(|| {
244                    DataError::new(DataErrorCode::Serialization, "expected object in `value`")
245                })?;
246            let name = m
247                .get("name")
248                .and_then(|v| v.as_str())
249                .ok_or_else(|| DataError::new(DataErrorCode::Serialization, "missing enum name"))?;
250            let val = if let Some(v) = m.get("value") {
251                Some(Box::new(decode_value(v)?))
252            } else {
253                None
254            };
255            Ok(Value::Enum(EnumValue {
256                name: name.to_string(),
257                value: val,
258            }))
259        }
260        other => Err(DataError::new(
261            DataErrorCode::Serialization,
262            format!("unknown value type tag `{other}`"),
263        )),
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn round_trip_value() {
273        let v = Value::String("hi".into());
274        let json = to_json(&v).expect("json");
275        let back = from_json(&json).expect("back");
276        assert_eq!(v, back);
277    }
278
279    #[test]
280    fn round_trip_bytes() {
281        let v = Value::Bytes(vec![1, 2, 3, 42].into());
282        let json = to_json(&v).expect("json");
283        let back = from_json(&json).expect("back");
284        assert_eq!(v, back);
285        assert!(json.contains("AQIDKg"));
286    }
287
288    #[test]
289    fn errors_on_invalid_json() {
290        let err = from_json("not-json").unwrap_err();
291        assert_eq!(err.code(), DataErrorCode::Serialization);
292    }
293
294    #[test]
295    fn errors_on_wrong_type_tag() {
296        let err = decode_value(&serde_json::json!({"type":"nope","value":1})).unwrap_err();
297        assert_eq!(err.code(), DataErrorCode::Serialization);
298    }
299
300    #[test]
301    fn rejects_non_finite_float() {
302        let err = to_json(&Value::Float(f64::INFINITY)).unwrap_err();
303        assert_eq!(err.code(), DataErrorCode::Serialization);
304    }
305
306    #[test]
307    fn rejects_bad_base64() {
308        let err = decode_value(&serde_json::json!({"type":"bytes","value":"!!!"})).unwrap_err();
309        assert_eq!(err.code(), DataErrorCode::Serialization);
310    }
311
312    #[test]
313    fn deterministic_field_order() {
314        let json = to_json(&Value::Int(5)).unwrap();
315        // Expect "type" before "value" to keep goldens stable.
316        assert!(json.find("\"type\"").unwrap() < json.find("\"value\"").unwrap());
317    }
318
319    #[test]
320    fn round_trip_struct_and_enum() {
321        let v = Value::Struct(vec![
322            StructFieldValue {
323                name: "a".into(),
324                value: Value::Int(1),
325            },
326            StructFieldValue {
327                name: "b".into(),
328                value: Value::Bool(true),
329            },
330        ]);
331        let json = to_json(&v).unwrap();
332        let back = from_json(&json).unwrap();
333        assert_eq!(back, v);
334
335        let e = Value::Enum(EnumValue {
336            name: "ok".into(),
337            value: Some(Box::new(Value::Int(2))),
338        });
339        let json = to_json(&e).unwrap();
340        let back = from_json(&json).unwrap();
341        assert_eq!(back, e);
342    }
343}