//! The [`JsonValue`] enum and all its conversions to/from primitive and CRDT types. use std::fmt::Display; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use crate::{keypair::AuthorId, list_crdt::ListCrdt, lww_crdt::LwwRegisterCrdt, op::PathSegment}; use super::{CrdtNode, CrdtNodeFromValue}; /// An enum representing a JSON value #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub enum JsonValue { #[default] Null, Bool(bool), Number(f64), String(String), Array(Vec), Object(IndexMap), } impl Display for JsonValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { JsonValue::Null => "null".to_string(), JsonValue::Bool(b) => b.to_string(), JsonValue::Number(n) => n.to_string(), JsonValue::String(s) => format!("\"{s}\""), JsonValue::Array(arr) => { if arr.len() > 1 { format!( "[\n{}\n]", arr.iter() .map(|x| format!(" {x}")) .collect::>() .join(",\n") ) } else { format!( "[ {} ]", arr.iter() .map(|x| x.to_string()) .collect::>() .join(", ") ) } } JsonValue::Object(obj) => format!( "{{ {} }}", obj.iter() .map(|(k, v)| format!(" \"{k}\": {v}")) .collect::>() .join(",\n") ), } ) } } /// Allow easy conversion to and from serde's JSON format. This allows us to use the [`json!`] /// macro impl From for serde_json::Value { fn from(value: JsonValue) -> Self { match value { JsonValue::Null => serde_json::Value::Null, JsonValue::Bool(x) => serde_json::Value::Bool(x), JsonValue::Number(x) => { serde_json::Value::Number(serde_json::Number::from_f64(x).unwrap()) } JsonValue::String(x) => serde_json::Value::String(x), JsonValue::Array(x) => { serde_json::Value::Array(x.iter().map(|a| a.clone().into()).collect()) } JsonValue::Object(x) => serde_json::Value::Object( x.iter() .map(|(k, v)| (k.clone(), v.clone().into())) .collect(), ), } } } impl From for JsonValue { fn from(value: serde_json::Value) -> Self { match value { serde_json::Value::Null => JsonValue::Null, serde_json::Value::Bool(x) => JsonValue::Bool(x), serde_json::Value::Number(x) => JsonValue::Number(x.as_f64().unwrap()), serde_json::Value::String(x) => JsonValue::String(x), serde_json::Value::Array(x) => { JsonValue::Array(x.iter().map(|a| a.clone().into()).collect()) } serde_json::Value::Object(x) => JsonValue::Object( x.iter() .map(|(k, v)| (k.clone(), v.clone().into())) .collect(), ), } } } impl JsonValue { pub fn into_json(self) -> serde_json::Value { self.into() } } /// Conversions from primitive types to [`JsonValue`] impl From for JsonValue { fn from(val: bool) -> Self { JsonValue::Bool(val) } } impl From for JsonValue { fn from(val: i64) -> Self { JsonValue::Number(val as f64) } } impl From for JsonValue { fn from(val: i32) -> Self { JsonValue::Number(val as f64) } } impl From for JsonValue { fn from(val: f64) -> Self { JsonValue::Number(val) } } impl From for JsonValue { fn from(val: String) -> Self { JsonValue::String(val) } } impl From for JsonValue { fn from(val: char) -> Self { JsonValue::String(val.into()) } } impl From> for JsonValue where T: CrdtNode, { fn from(val: Option) -> Self { match val { Some(x) => x.view(), None => JsonValue::Null, } } } impl From> for JsonValue where T: CrdtNode, { fn from(value: Vec) -> Self { JsonValue::Array(value.iter().map(|x| x.view()).collect()) } } /// Conversions from bool to CRDT impl CrdtNodeFromValue for bool { fn node_from(value: JsonValue, _id: AuthorId, _path: Vec) -> Result { if let JsonValue::Bool(x) = value { Ok(x) } else { Err(format!("failed to convert {value:?} -> bool")) } } } /// Conversions from f64 to CRDT impl CrdtNodeFromValue for f64 { fn node_from(value: JsonValue, _id: AuthorId, _path: Vec) -> Result { if let JsonValue::Number(x) = value { Ok(x) } else { Err(format!("failed to convert {value:?} -> f64")) } } } /// Conversions from i64 to CRDT impl CrdtNodeFromValue for i64 { fn node_from(value: JsonValue, _id: AuthorId, _path: Vec) -> Result { if let JsonValue::Number(x) = value { Ok(x as i64) } else { Err(format!("failed to convert {value:?} -> f64")) } } } /// Conversions from String to CRDT impl CrdtNodeFromValue for String { fn node_from(value: JsonValue, _id: AuthorId, _path: Vec) -> Result { if let JsonValue::String(x) = value { Ok(x) } else { Err(format!("failed to convert {value:?} -> String")) } } } /// Conversions from char to CRDT impl CrdtNodeFromValue for char { fn node_from(value: JsonValue, _id: AuthorId, _path: Vec) -> Result { if let JsonValue::String(x) = value.clone() { x.chars().next().ok_or(format!( "failed to convert {value:?} -> char: found a zero-length string" )) } else { Err(format!("failed to convert {value:?} -> char")) } } } impl CrdtNodeFromValue for LwwRegisterCrdt where T: CrdtNode, { fn node_from(value: JsonValue, id: AuthorId, path: Vec) -> Result { let mut crdt = LwwRegisterCrdt::new(id, path); crdt.set(value); Ok(crdt) } } impl CrdtNodeFromValue for ListCrdt where T: CrdtNode, { fn node_from(value: JsonValue, id: AuthorId, path: Vec) -> Result { if let JsonValue::Array(arr) = value { let mut crdt = ListCrdt::new(id, path); let result: Result<(), String> = arr.into_iter().enumerate().try_for_each(|(i, val)| { crdt.insert_idx(i, val); Ok(()) }); result?; Ok(crdt) } else { Err(format!("failed to convert {value:?} -> ListCRDT")) } } }