2026-04-28 15:47:31 +00:00
|
|
|
//! 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
|
2026-05-14 14:48:49 +01:00
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
2026-04-28 15:47:31 +00:00
|
|
|
pub enum JsonValue {
|
2026-05-14 14:48:49 +01:00
|
|
|
#[default]
|
2026-04-28 15:47:31 +00:00
|
|
|
Null,
|
|
|
|
|
Bool(bool),
|
|
|
|
|
Number(f64),
|
|
|
|
|
String(String),
|
|
|
|
|
Array(Vec<JsonValue>),
|
|
|
|
|
Object(IndexMap<String, JsonValue>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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::<Vec<_>>()
|
|
|
|
|
.join(",\n")
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
format!(
|
|
|
|
|
"[ {} ]",
|
|
|
|
|
arr.iter()
|
|
|
|
|
.map(|x| x.to_string())
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ")
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
JsonValue::Object(obj) => format!(
|
|
|
|
|
"{{ {} }}",
|
|
|
|
|
obj.iter()
|
|
|
|
|
.map(|(k, v)| format!(" \"{k}\": {v}"))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(",\n")
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Allow easy conversion to and from serde's JSON format. This allows us to use the [`json!`]
|
|
|
|
|
/// macro
|
|
|
|
|
impl From<JsonValue> 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<serde_json::Value> 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<bool> for JsonValue {
|
|
|
|
|
fn from(val: bool) -> Self {
|
|
|
|
|
JsonValue::Bool(val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<i64> for JsonValue {
|
|
|
|
|
fn from(val: i64) -> Self {
|
|
|
|
|
JsonValue::Number(val as f64)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<i32> for JsonValue {
|
|
|
|
|
fn from(val: i32) -> Self {
|
|
|
|
|
JsonValue::Number(val as f64)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<f64> for JsonValue {
|
|
|
|
|
fn from(val: f64) -> Self {
|
|
|
|
|
JsonValue::Number(val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<String> for JsonValue {
|
|
|
|
|
fn from(val: String) -> Self {
|
|
|
|
|
JsonValue::String(val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<char> for JsonValue {
|
|
|
|
|
fn from(val: char) -> Self {
|
|
|
|
|
JsonValue::String(val.into())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> From<Option<T>> for JsonValue
|
|
|
|
|
where
|
|
|
|
|
T: CrdtNode,
|
|
|
|
|
{
|
|
|
|
|
fn from(val: Option<T>) -> Self {
|
|
|
|
|
match val {
|
|
|
|
|
Some(x) => x.view(),
|
|
|
|
|
None => JsonValue::Null,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> From<Vec<T>> for JsonValue
|
|
|
|
|
where
|
|
|
|
|
T: CrdtNode,
|
|
|
|
|
{
|
|
|
|
|
fn from(value: Vec<T>) -> 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<PathSegment>) -> Result<Self, String> {
|
|
|
|
|
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<PathSegment>) -> Result<Self, String> {
|
|
|
|
|
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<PathSegment>) -> Result<Self, String> {
|
|
|
|
|
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<PathSegment>) -> Result<Self, String> {
|
|
|
|
|
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<PathSegment>) -> Result<Self, String> {
|
|
|
|
|
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<T> CrdtNodeFromValue for LwwRegisterCrdt<T>
|
|
|
|
|
where
|
|
|
|
|
T: CrdtNode,
|
|
|
|
|
{
|
|
|
|
|
fn node_from(value: JsonValue, id: AuthorId, path: Vec<PathSegment>) -> Result<Self, String> {
|
|
|
|
|
let mut crdt = LwwRegisterCrdt::new(id, path);
|
|
|
|
|
crdt.set(value);
|
|
|
|
|
Ok(crdt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> CrdtNodeFromValue for ListCrdt<T>
|
|
|
|
|
where
|
|
|
|
|
T: CrdtNode,
|
|
|
|
|
{
|
|
|
|
|
fn node_from(value: JsonValue, id: AuthorId, path: Vec<PathSegment>) -> Result<Self, String> {
|
|
|
|
|
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<T>"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|