Files
huskies/crates/bft-json-crdt/src/json_crdt/value.rs
T

263 lines
7.4 KiB
Rust
Raw Normal View History

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
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum JsonValue {
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")
),
}
)
}
}
impl Default for JsonValue {
fn default() -> Self {
Self::Null
}
}
/// 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>"))
}
}
}