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