huskies: merge 794
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
//! 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>"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user