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_from_vec, list_view};
5
6mod parser;
7
8#[derive(Debug, Clone, PartialEq)]
9pub enum JsonValue {
10    Null,
11    Bool(bool),
12    Int(i64),
13    Float(f64),
14    String(String),
15    Array(Vec<JsonValue>),
16    Object(BTreeMap<String, JsonValue>),
17}
18
19pub fn parse_json(input: &str) -> Result<JsonValue, String> {
20    parser::parse_json(input)
21}
22
23pub fn json_to_string(value: &JsonValue) -> String {
24    let mut out = String::new();
25    write_json_compact(&mut out, value);
26    out
27}
28
29pub fn format_json(value: &JsonValue) -> String {
30    let mut out = String::new();
31    write_json_pretty(&mut out, value, 0);
32    out
33}
34
35fn get_required<'a>(
36    obj: &'a BTreeMap<String, JsonValue>,
37    key: &str,
38    path: &str,
39) -> Result<&'a JsonValue, String> {
40    obj.get(key)
41        .ok_or_else(|| format!("{}: missing required field '{}'", path, key))
42}
43
44fn expect_object<'a>(
45    value: &'a JsonValue,
46    path: &str,
47) -> Result<&'a BTreeMap<String, JsonValue>, String> {
48    match value {
49        JsonValue::Object(obj) => Ok(obj),
50        _ => Err(format!("{} must be an object", path)),
51    }
52}
53
54fn parse_array<'a>(value: &'a JsonValue, path: &str) -> Result<&'a Vec<JsonValue>, String> {
55    match value {
56        JsonValue::Array(arr) => Ok(arr),
57        _ => Err(format!("{} must be an array", path)),
58    }
59}
60
61fn parse_string<'a>(value: &'a JsonValue, path: &str) -> Result<&'a str, String> {
62    match value {
63        JsonValue::String(s) => Ok(s),
64        _ => Err(format!("{} must be a string", path)),
65    }
66}
67
68pub fn values_to_json(values: &[Value]) -> Result<Vec<JsonValue>, String> {
69    values.iter().map(value_to_json).collect()
70}
71
72pub fn values_to_json_lossy(values: &[Value]) -> Vec<JsonValue> {
73    values.iter().map(value_to_json_lossy).collect()
74}
75
76pub fn json_values_to_values(values: &[JsonValue]) -> Result<Vec<Value>, String> {
77    values.iter().map(json_to_value).collect()
78}
79
80pub fn value_to_json(value: &Value) -> Result<JsonValue, String> {
81    if let Some(items) = list_view(value) {
82        let mut arr = Vec::with_capacity(items.len());
83        for item in items.iter() {
84            arr.push(value_to_json(item)?);
85        }
86        return Ok(JsonValue::Array(arr));
87    }
88
89    match value {
90        Value::Int(i) => Ok(JsonValue::Int(*i)),
91        Value::Float(f) => {
92            if !f.is_finite() {
93                return Err("cannot serialize non-finite float (NaN/inf)".to_string());
94            }
95            Ok(JsonValue::Float(*f))
96        }
97        Value::Str(s) => Ok(JsonValue::String(s.clone())),
98        Value::Bool(b) => Ok(JsonValue::Bool(*b)),
99        Value::Unit => Ok(JsonValue::Null),
100        Value::Ok(inner) => Ok(wrap_marker("$ok", value_to_json(inner)?)),
101        Value::Err(inner) => Ok(wrap_marker("$err", value_to_json(inner)?)),
102        Value::Some(inner) => Ok(wrap_marker("$some", value_to_json(inner)?)),
103        Value::None => Ok(wrap_marker("$none", JsonValue::Bool(true))),
104        Value::List(_) => unreachable!("handled via list_view above"),
105        Value::Tuple(items) => {
106            let mut arr = Vec::with_capacity(items.len());
107            for item in items {
108                arr.push(value_to_json(item)?);
109            }
110            Ok(wrap_marker("$tuple", JsonValue::Array(arr)))
111        }
112        Value::Map(entries) => {
113            if entries.keys().all(|k| matches!(k, Value::Str(_))) {
114                let mut obj = BTreeMap::new();
115                for (k, v) in entries {
116                    let Value::Str(key) = k else {
117                        unreachable!("checked above");
118                    };
119                    obj.insert(key.clone(), value_to_json(v)?);
120                }
121                Ok(JsonValue::Object(obj))
122            } else {
123                let mut pairs = Vec::with_capacity(entries.len());
124                for (k, v) in entries {
125                    pairs.push(JsonValue::Array(vec![value_to_json(k)?, value_to_json(v)?]));
126                }
127                Ok(wrap_marker("$map", JsonValue::Array(pairs)))
128            }
129        }
130        Value::Record { type_name, fields } => {
131            let mut fields_obj = BTreeMap::new();
132            for (name, field_value) in fields.iter() {
133                fields_obj.insert(name.clone(), value_to_json(field_value)?);
134            }
135            let mut payload = BTreeMap::new();
136            payload.insert("type".to_string(), JsonValue::String(type_name.clone()));
137            payload.insert("fields".to_string(), JsonValue::Object(fields_obj));
138            Ok(wrap_marker("$record", JsonValue::Object(payload)))
139        }
140        Value::Variant {
141            type_name,
142            variant,
143            fields,
144        } => {
145            let mut field_vals = Vec::with_capacity(fields.len());
146            for field in fields.iter() {
147                field_vals.push(value_to_json(field)?);
148            }
149            let mut payload = BTreeMap::new();
150            payload.insert("type".to_string(), JsonValue::String(type_name.clone()));
151            payload.insert("name".to_string(), JsonValue::String(variant.clone()));
152            payload.insert("fields".to_string(), JsonValue::Array(field_vals));
153            Ok(wrap_marker("$variant", JsonValue::Object(payload)))
154        }
155        Value::Fn(_) | Value::Builtin(_) | Value::Namespace { .. } => Err(format!(
156            "cannot serialize non-replay-safe value: {}",
157            aver_repr(value)
158        )),
159    }
160}
161
162pub fn json_to_value(json: &JsonValue) -> Result<Value, String> {
163    match json {
164        JsonValue::Null => Ok(Value::Unit),
165        JsonValue::Bool(b) => Ok(Value::Bool(*b)),
166        JsonValue::Int(i) => Ok(Value::Int(*i)),
167        JsonValue::Float(f) => Ok(Value::Float(*f)),
168        JsonValue::String(s) => Ok(Value::Str(s.clone())),
169        JsonValue::Array(items) => {
170            let mut out = Vec::with_capacity(items.len());
171            for item in items {
172                out.push(json_to_value(item)?);
173            }
174            Ok(list_from_vec(out))
175        }
176        JsonValue::Object(obj) => {
177            if let Some((marker, payload)) = marker_single_key(obj) {
178                return decode_marker(marker, payload);
179            }
180            let mut map = HashMap::with_capacity(obj.len());
181            for (k, v) in obj {
182                map.insert(Value::Str(k.clone()), json_to_value(v)?);
183            }
184            Ok(Value::Map(map))
185        }
186    }
187}
188
189pub fn first_diff_path(expected: &JsonValue, got: &JsonValue) -> Option<String> {
190    first_diff_path_inner(expected, got, "$")
191}
192
193pub fn value_to_json_lossy(value: &Value) -> JsonValue {
194    match value_to_json(value) {
195        Ok(v) => v,
196        Err(_) => {
197            let mut obj = BTreeMap::new();
198            obj.insert("$opaque".to_string(), JsonValue::String(aver_repr(value)));
199            JsonValue::Object(obj)
200        }
201    }
202}
203
204fn wrap_marker(name: &str, value: JsonValue) -> JsonValue {
205    let mut obj = BTreeMap::new();
206    obj.insert(name.to_string(), value);
207    JsonValue::Object(obj)
208}
209
210fn marker_single_key(obj: &BTreeMap<String, JsonValue>) -> Option<(&str, &JsonValue)> {
211    if obj.len() != 1 {
212        return None;
213    }
214    obj.iter().next().map(|(k, v)| (k.as_str(), v))
215}
216
217fn decode_marker(marker: &str, payload: &JsonValue) -> Result<Value, String> {
218    match marker {
219        "$ok" => Ok(Value::Ok(Box::new(json_to_value(payload)?))),
220        "$err" => Ok(Value::Err(Box::new(json_to_value(payload)?))),
221        "$some" => Ok(Value::Some(Box::new(json_to_value(payload)?))),
222        "$none" => Ok(Value::None),
223        "$tuple" => decode_tuple(payload),
224        "$map" => decode_map(payload),
225        "$record" => decode_record(payload),
226        "$variant" => decode_variant(payload),
227        _ => Err(format!("unknown replay marker '{}'", marker)),
228    }
229}
230
231fn decode_tuple(payload: &JsonValue) -> Result<Value, String> {
232    let items = parse_array(payload, "$tuple")?;
233    let mut out = Vec::with_capacity(items.len());
234    for item in items {
235        out.push(json_to_value(item)?);
236    }
237    Ok(Value::Tuple(out))
238}
239
240fn decode_map(payload: &JsonValue) -> Result<Value, String> {
241    let pairs = parse_array(payload, "$map")?;
242    let mut out = HashMap::with_capacity(pairs.len());
243    for (idx, pair_json) in pairs.iter().enumerate() {
244        let pair = parse_array(pair_json, &format!("$map[{}]", idx))?;
245        if pair.len() != 2 {
246            return Err(format!("$map[{}] must be a 2-element array", idx));
247        }
248        let key = json_to_value(&pair[0])?;
249        let value = json_to_value(&pair[1])?;
250        out.insert(key, value);
251    }
252    Ok(Value::Map(out))
253}
254
255fn decode_record(payload: &JsonValue) -> Result<Value, String> {
256    let obj = expect_object(payload, "$record")?;
257    let type_name =
258        parse_string(get_required(obj, "type", "$record")?, "$record.type")?.to_string();
259    let fields_obj = expect_object(get_required(obj, "fields", "$record")?, "$record.fields")?;
260    let mut fields = Vec::with_capacity(fields_obj.len());
261    for (key, field_val) in fields_obj {
262        fields.push((key.clone(), json_to_value(field_val)?));
263    }
264    Ok(Value::Record {
265        type_name,
266        fields: fields.into(),
267    })
268}
269
270fn decode_variant(payload: &JsonValue) -> Result<Value, String> {
271    let obj = expect_object(payload, "$variant")?;
272    let type_name =
273        parse_string(get_required(obj, "type", "$variant")?, "$variant.type")?.to_string();
274    let variant =
275        parse_string(get_required(obj, "name", "$variant")?, "$variant.name")?.to_string();
276    let fields_arr = parse_array(get_required(obj, "fields", "$variant")?, "$variant.fields")?;
277    let mut fields = Vec::with_capacity(fields_arr.len());
278    for val in fields_arr {
279        fields.push(json_to_value(val)?);
280    }
281    Ok(Value::Variant {
282        type_name,
283        variant,
284        fields: fields.into(),
285    })
286}
287
288fn first_diff_path_inner(expected: &JsonValue, got: &JsonValue, path: &str) -> Option<String> {
289    match (expected, got) {
290        (JsonValue::Object(a), JsonValue::Object(b)) => {
291            let mut keys = a.keys().chain(b.keys()).cloned().collect::<Vec<_>>();
292            keys.sort();
293            keys.dedup();
294            for key in keys {
295                let next_path = if path == "$" {
296                    format!("$.{}", key)
297                } else {
298                    format!("{}.{}", path, key)
299                };
300                match (a.get(&key), b.get(&key)) {
301                    (Some(av), Some(bv)) => {
302                        if let Some(diff) = first_diff_path_inner(av, bv, &next_path) {
303                            return Some(diff);
304                        }
305                    }
306                    _ => return Some(next_path),
307                }
308            }
309            None
310        }
311        (JsonValue::Array(a), JsonValue::Array(b)) => {
312            if a.len() != b.len() {
313                return Some(format!("{}[len]", path));
314            }
315            for (idx, (av, bv)) in a.iter().zip(b.iter()).enumerate() {
316                let next_path = format!("{}[{}]", path, idx);
317                if let Some(diff) = first_diff_path_inner(av, bv, &next_path) {
318                    return Some(diff);
319                }
320            }
321            None
322        }
323        _ => {
324            if expected == got {
325                None
326            } else {
327                Some(path.to_string())
328            }
329        }
330    }
331}
332
333fn write_json_compact(out: &mut String, value: &JsonValue) {
334    match value {
335        JsonValue::Null => out.push_str("null"),
336        JsonValue::Bool(true) => out.push_str("true"),
337        JsonValue::Bool(false) => out.push_str("false"),
338        JsonValue::Int(i) => {
339            let _ = write!(out, "{}", i);
340        }
341        JsonValue::Float(f) => out.push_str(&format_float(*f)),
342        JsonValue::String(s) => write_json_string(out, s),
343        JsonValue::Array(arr) => {
344            out.push('[');
345            for (idx, item) in arr.iter().enumerate() {
346                if idx > 0 {
347                    out.push(',');
348                }
349                write_json_compact(out, item);
350            }
351            out.push(']');
352        }
353        JsonValue::Object(obj) => {
354            out.push('{');
355            for (idx, (k, v)) in obj.iter().enumerate() {
356                if idx > 0 {
357                    out.push(',');
358                }
359                write_json_string(out, k);
360                out.push(':');
361                write_json_compact(out, v);
362            }
363            out.push('}');
364        }
365    }
366}
367
368fn write_json_pretty(out: &mut String, value: &JsonValue, indent: usize) {
369    match value {
370        JsonValue::Array(arr) => {
371            if arr.is_empty() {
372                out.push_str("[]");
373                return;
374            }
375            out.push_str("[\n");
376            for (idx, item) in arr.iter().enumerate() {
377                push_indent(out, indent + 2);
378                write_json_pretty(out, item, indent + 2);
379                if idx + 1 < arr.len() {
380                    out.push(',');
381                }
382                out.push('\n');
383            }
384            push_indent(out, indent);
385            out.push(']');
386        }
387        JsonValue::Object(obj) => {
388            if obj.is_empty() {
389                out.push_str("{}");
390                return;
391            }
392            out.push_str("{\n");
393            for (idx, (k, v)) in obj.iter().enumerate() {
394                push_indent(out, indent + 2);
395                write_json_string(out, k);
396                out.push_str(": ");
397                write_json_pretty(out, v, indent + 2);
398                if idx + 1 < obj.len() {
399                    out.push(',');
400                }
401                out.push('\n');
402            }
403            push_indent(out, indent);
404            out.push('}');
405        }
406        _ => write_json_compact(out, value),
407    }
408}
409
410fn push_indent(out: &mut String, indent: usize) {
411    for _ in 0..indent {
412        out.push(' ');
413    }
414}
415
416fn write_json_string(out: &mut String, s: &str) {
417    out.push('"');
418    for ch in s.chars() {
419        match ch {
420            '"' => out.push_str("\\\""),
421            '\\' => out.push_str("\\\\"),
422            '\n' => out.push_str("\\n"),
423            '\r' => out.push_str("\\r"),
424            '\t' => out.push_str("\\t"),
425            '\u{08}' => out.push_str("\\b"),
426            '\u{0C}' => out.push_str("\\f"),
427            c if c < '\u{20}' => {
428                let _ = write!(out, "\\u{:04X}", c as u32);
429            }
430            c => out.push(c),
431        }
432    }
433    out.push('"');
434}
435
436fn format_float(f: f64) -> String {
437    let mut s = format!("{}", f);
438    if !s.contains('.') && !s.contains('e') && !s.contains('E') {
439        s.push_str(".0");
440    }
441    s
442}