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::Vector(vec) => {
156 let mut arr = Vec::with_capacity(vec.len());
157 for item in vec.iter() {
158 arr.push(value_to_json(item)?);
159 }
160 Ok(wrap_marker("$vector", JsonValue::Array(arr)))
161 }
162 Value::Fn(_) | Value::Builtin(_) | Value::Namespace { .. } => Err(format!(
163 "cannot serialize non-replay-safe value: {}",
164 aver_repr(value)
165 )),
166 }
167}
168
169pub fn json_to_value(json: &JsonValue) -> Result<Value, String> {
170 match json {
171 JsonValue::Null => Ok(Value::Unit),
172 JsonValue::Bool(b) => Ok(Value::Bool(*b)),
173 JsonValue::Int(i) => Ok(Value::Int(*i)),
174 JsonValue::Float(f) => Ok(Value::Float(*f)),
175 JsonValue::String(s) => Ok(Value::Str(s.clone())),
176 JsonValue::Array(items) => {
177 let mut out = Vec::with_capacity(items.len());
178 for item in items {
179 out.push(json_to_value(item)?);
180 }
181 Ok(list_from_vec(out))
182 }
183 JsonValue::Object(obj) => {
184 if let Some((marker, payload)) = marker_single_key(obj) {
185 return decode_marker(marker, payload);
186 }
187 let mut map = HashMap::with_capacity(obj.len());
188 for (k, v) in obj {
189 map.insert(Value::Str(k.clone()), json_to_value(v)?);
190 }
191 Ok(Value::Map(map))
192 }
193 }
194}
195
196pub fn first_diff_path(expected: &JsonValue, got: &JsonValue) -> Option<String> {
197 first_diff_path_inner(expected, got, "$")
198}
199
200pub fn value_to_json_lossy(value: &Value) -> JsonValue {
201 match value_to_json(value) {
202 Ok(v) => v,
203 Err(_) => {
204 let mut obj = BTreeMap::new();
205 obj.insert("$opaque".to_string(), JsonValue::String(aver_repr(value)));
206 JsonValue::Object(obj)
207 }
208 }
209}
210
211fn wrap_marker(name: &str, value: JsonValue) -> JsonValue {
212 let mut obj = BTreeMap::new();
213 obj.insert(name.to_string(), value);
214 JsonValue::Object(obj)
215}
216
217fn marker_single_key(obj: &BTreeMap<String, JsonValue>) -> Option<(&str, &JsonValue)> {
218 if obj.len() != 1 {
219 return None;
220 }
221 obj.iter().next().and_then(|(k, v)| {
231 if k.starts_with('$') {
232 Some((k.as_str(), v))
233 } else {
234 None
235 }
236 })
237}
238
239fn decode_marker(marker: &str, payload: &JsonValue) -> Result<Value, String> {
240 match marker {
241 "$ok" => Ok(Value::Ok(Box::new(json_to_value(payload)?))),
242 "$err" => Ok(Value::Err(Box::new(json_to_value(payload)?))),
243 "$some" => Ok(Value::Some(Box::new(json_to_value(payload)?))),
244 "$none" => Ok(Value::None),
245 "$tuple" => decode_tuple(payload),
246 "$map" => decode_map(payload),
247 "$record" => decode_record(payload),
248 "$variant" => decode_variant(payload),
249 "$vector" => decode_vector(payload),
250 _ => Err(format!("unknown replay marker '{}'", marker)),
251 }
252}
253
254fn decode_vector(payload: &JsonValue) -> Result<Value, String> {
261 let items = parse_array(payload, "$vector")?;
262 let mut out = Vec::with_capacity(items.len());
263 for item in items {
264 out.push(json_to_value(item)?);
265 }
266 Ok(Value::Vector(aver_rt::AverVector::from_vec(out)))
267}
268
269fn decode_tuple(payload: &JsonValue) -> Result<Value, String> {
270 let items = parse_array(payload, "$tuple")?;
271 let mut out = Vec::with_capacity(items.len());
272 for item in items {
273 out.push(json_to_value(item)?);
274 }
275 Ok(Value::Tuple(out))
276}
277
278fn decode_map(payload: &JsonValue) -> Result<Value, String> {
279 let pairs = parse_array(payload, "$map")?;
280 let mut out = HashMap::with_capacity(pairs.len());
281 for (idx, pair_json) in pairs.iter().enumerate() {
282 let pair = parse_array(pair_json, &format!("$map[{}]", idx))?;
283 if pair.len() != 2 {
284 return Err(format!("$map[{}] must be a 2-element array", idx));
285 }
286 let key = json_to_value(&pair[0])?;
287 let value = json_to_value(&pair[1])?;
288 out.insert(key, value);
289 }
290 Ok(Value::Map(out))
291}
292
293fn decode_record(payload: &JsonValue) -> Result<Value, String> {
294 let obj = expect_object(payload, "$record")?;
295 let type_name =
296 parse_string(get_required(obj, "type", "$record")?, "$record.type")?.to_string();
297 let fields_obj = expect_object(get_required(obj, "fields", "$record")?, "$record.fields")?;
298 let mut fields = Vec::with_capacity(fields_obj.len());
299 for (key, field_val) in fields_obj {
300 fields.push((key.clone(), json_to_value(field_val)?));
301 }
302 Ok(Value::Record {
303 type_name,
304 fields: fields.into(),
305 })
306}
307
308fn decode_variant(payload: &JsonValue) -> Result<Value, String> {
309 let obj = expect_object(payload, "$variant")?;
310 let type_name =
311 parse_string(get_required(obj, "type", "$variant")?, "$variant.type")?.to_string();
312 let variant =
313 parse_string(get_required(obj, "name", "$variant")?, "$variant.name")?.to_string();
314 let fields_arr = parse_array(get_required(obj, "fields", "$variant")?, "$variant.fields")?;
315 let mut fields = Vec::with_capacity(fields_arr.len());
316 for val in fields_arr {
317 fields.push(json_to_value(val)?);
318 }
319 Ok(Value::Variant {
320 type_name,
321 variant,
322 fields: fields.into(),
323 })
324}
325
326fn first_diff_path_inner(expected: &JsonValue, got: &JsonValue, path: &str) -> Option<String> {
327 match (expected, got) {
328 (JsonValue::Object(a), JsonValue::Object(b)) => {
329 let mut keys = a.keys().chain(b.keys()).cloned().collect::<Vec<_>>();
330 keys.sort();
331 keys.dedup();
332 for key in keys {
333 let next_path = if path == "$" {
334 format!("$.{}", key)
335 } else {
336 format!("{}.{}", path, key)
337 };
338 match (a.get(&key), b.get(&key)) {
339 (Some(av), Some(bv)) => {
340 if let Some(diff) = first_diff_path_inner(av, bv, &next_path) {
341 return Some(diff);
342 }
343 }
344 _ => return Some(next_path),
345 }
346 }
347 None
348 }
349 (JsonValue::Array(a), JsonValue::Array(b)) => {
350 if a.len() != b.len() {
351 return Some(format!("{}[len]", path));
352 }
353 for (idx, (av, bv)) in a.iter().zip(b.iter()).enumerate() {
354 let next_path = format!("{}[{}]", path, idx);
355 if let Some(diff) = first_diff_path_inner(av, bv, &next_path) {
356 return Some(diff);
357 }
358 }
359 None
360 }
361 _ => {
362 if expected == got {
363 None
364 } else {
365 Some(path.to_string())
366 }
367 }
368 }
369}
370
371fn write_json_compact(out: &mut String, value: &JsonValue) {
372 match value {
373 JsonValue::Null => out.push_str("null"),
374 JsonValue::Bool(true) => out.push_str("true"),
375 JsonValue::Bool(false) => out.push_str("false"),
376 JsonValue::Int(i) => {
377 let _ = write!(out, "{}", i);
378 }
379 JsonValue::Float(f) => out.push_str(&format_float(*f)),
380 JsonValue::String(s) => write_json_string(out, s),
381 JsonValue::Array(arr) => {
382 out.push('[');
383 for (idx, item) in arr.iter().enumerate() {
384 if idx > 0 {
385 out.push(',');
386 }
387 write_json_compact(out, item);
388 }
389 out.push(']');
390 }
391 JsonValue::Object(obj) => {
392 out.push('{');
393 for (idx, (k, v)) in obj.iter().enumerate() {
394 if idx > 0 {
395 out.push(',');
396 }
397 write_json_string(out, k);
398 out.push(':');
399 write_json_compact(out, v);
400 }
401 out.push('}');
402 }
403 }
404}
405
406fn write_json_pretty(out: &mut String, value: &JsonValue, indent: usize) {
407 match value {
408 JsonValue::Array(arr) => {
409 if arr.is_empty() {
410 out.push_str("[]");
411 return;
412 }
413 out.push_str("[\n");
414 for (idx, item) in arr.iter().enumerate() {
415 push_indent(out, indent + 2);
416 write_json_pretty(out, item, indent + 2);
417 if idx + 1 < arr.len() {
418 out.push(',');
419 }
420 out.push('\n');
421 }
422 push_indent(out, indent);
423 out.push(']');
424 }
425 JsonValue::Object(obj) => {
426 if obj.is_empty() {
427 out.push_str("{}");
428 return;
429 }
430 out.push_str("{\n");
431 for (idx, (k, v)) in obj.iter().enumerate() {
432 push_indent(out, indent + 2);
433 write_json_string(out, k);
434 out.push_str(": ");
435 write_json_pretty(out, v, indent + 2);
436 if idx + 1 < obj.len() {
437 out.push(',');
438 }
439 out.push('\n');
440 }
441 push_indent(out, indent);
442 out.push('}');
443 }
444 _ => write_json_compact(out, value),
445 }
446}
447
448fn push_indent(out: &mut String, indent: usize) {
449 for _ in 0..indent {
450 out.push(' ');
451 }
452}
453
454fn write_json_string(out: &mut String, s: &str) {
455 out.push('"');
456 for ch in s.chars() {
457 match ch {
458 '"' => out.push_str("\\\""),
459 '\\' => out.push_str("\\\\"),
460 '\n' => out.push_str("\\n"),
461 '\r' => out.push_str("\\r"),
462 '\t' => out.push_str("\\t"),
463 '\u{08}' => out.push_str("\\b"),
464 '\u{0C}' => out.push_str("\\f"),
465 c if c < '\u{20}' => {
466 let _ = write!(out, "\\u{:04X}", c as u32);
467 }
468 c => out.push(c),
469 }
470 }
471 out.push('"');
472}
473
474fn format_float(f: f64) -> String {
475 let mut s = format!("{}", f);
476 if !s.contains('.') && !s.contains('e') && !s.contains('E') {
477 s.push_str(".0");
478 }
479 s
480}