From 3120ceee5d339c0878199a1bf877e02456ab5e8d Mon Sep 17 00:00:00 2001 From: Dave Hrycyszyn Date: Thu, 30 May 2024 15:45:38 +0100 Subject: [PATCH] Renamed Value to JsonValue to make things a little more clear --- crates/bft-json-crdt/benches/speed.rs | 4 +- .../bft-json-crdt/bft-crdt-derive/src/lib.rs | 10 +- crates/bft-json-crdt/src/json_crdt.rs | 152 +++++++++--------- crates/bft-json-crdt/src/list_crdt.rs | 18 ++- crates/bft-json-crdt/src/lww_crdt.rs | 10 +- crates/bft-json-crdt/src/op.rs | 4 +- crates/bft-json-crdt/tests/commutative.rs | 12 +- 7 files changed, 108 insertions(+), 102 deletions(-) diff --git a/crates/bft-json-crdt/benches/speed.rs b/crates/bft-json-crdt/benches/speed.rs index b4c3f78..9f26d63 100644 --- a/crates/bft-json-crdt/benches/speed.rs +++ b/crates/bft-json-crdt/benches/speed.rs @@ -2,7 +2,7 @@ extern crate test; use bft_json_crdt::{ - json_crdt::Value, keypair::make_author, list_crdt::ListCrdt, op::Op, op::ROOT_ID, + json_crdt::JsonValue, keypair::make_author, list_crdt::ListCrdt, op::Op, op::ROOT_ID, }; use rand::seq::SliceRandom; use test::Bencher; @@ -35,7 +35,7 @@ fn bench_insert_many_agents_conflicts(b: &mut Bencher) { const N: u8 = 50; let mut rng = rand::thread_rng(); let mut crdts: Vec> = Vec::with_capacity(N as usize); - let mut logs: Vec> = Vec::new(); + let mut logs: Vec> = Vec::new(); for i in 0..N { let list = ListCrdt::new(make_author(i), vec![]); crdts.push(list); diff --git a/crates/bft-json-crdt/bft-crdt-derive/src/lib.rs b/crates/bft-json-crdt/bft-crdt-derive/src/lib.rs index d4d1df1..f76813a 100644 --- a/crates/bft-json-crdt/bft-crdt-derive/src/lib.rs +++ b/crates/bft-json-crdt/bft-crdt-derive/src/lib.rs @@ -90,8 +90,8 @@ pub fn derive_json_crdt(input: OgTokenStream) -> OgTokenStream { let expanded = quote! { impl #impl_generics #crate_name::json_crdt::CrdtNodeFromValue for #ident #ty_generics #where_clause { - fn node_from(value: #crate_name::json_crdt::Value, id: #crate_name::keypair::AuthorId, path: Vec<#crate_name::op::PathSegment>) -> Result { - if let #crate_name::json_crdt::Value::Object(mut obj) = value { + fn node_from(value: #crate_name::json_crdt::JsonValue, id: #crate_name::keypair::AuthorId, path: Vec<#crate_name::op::PathSegment>) -> Result { + if let #crate_name::json_crdt::JsonValue::Object(mut obj) = value { Ok(#ident { path: path.clone(), id, @@ -119,7 +119,7 @@ pub fn derive_json_crdt(input: OgTokenStream) -> OgTokenStream { } impl #impl_generics #crate_name::json_crdt::CrdtNode for #ident #ty_generics #where_clause { - fn apply(&mut self, op: #crate_name::op::Op<#crate_name::json_crdt::Value>) -> #crate_name::json_crdt::OpState { + fn apply(&mut self, op: #crate_name::op::Op<#crate_name::json_crdt::JsonValue>) -> #crate_name::json_crdt::OpState { let path = op.path.clone(); let author = op.id.clone(); if !#crate_name::op::ensure_subpath(&self.path, &op.path) { @@ -143,10 +143,10 @@ pub fn derive_json_crdt(input: OgTokenStream) -> OgTokenStream { } } - fn view(&self) -> #crate_name::json_crdt::Value { + fn view(&self) -> #crate_name::json_crdt::JsonValue { let mut view_map = std::collections::HashMap::new(); #(view_map.insert(#ident_strings.to_string(), self.#ident_literals.view().into());)* - #crate_name::json_crdt::Value::Object(view_map) + #crate_name::json_crdt::JsonValue::Object(view_map) } fn new(id: #crate_name::keypair::AuthorId, path: Vec<#crate_name::op::PathSegment>) -> Self { diff --git a/crates/bft-json-crdt/src/json_crdt.rs b/crates/bft-json-crdt/src/json_crdt.rs index 2488c50..3593efb 100644 --- a/crates/bft-json-crdt/src/json_crdt.rs +++ b/crates/bft-json-crdt/src/json_crdt.rs @@ -23,9 +23,9 @@ pub trait CrdtNode: CrdtNodeFromValue + Hashable + Clone { /// Create a new CRDT of this type fn new(id: AuthorId, path: Vec) -> Self; /// Apply an operation to this CRDT, forwarding if necessary - fn apply(&mut self, op: Op) -> OpState; + fn apply(&mut self, op: Op) -> OpState; /// Get a JSON representation of the value in this node - fn view(&self) -> Value; + fn view(&self) -> JsonValue; } /// Enum representing possible outcomes of applying an operation to a CRDT @@ -60,14 +60,14 @@ pub enum OpState { } /// The following types can be used as a 'terminal' type in CRDTs -pub trait MarkPrimitive: Into + Default {} +pub trait MarkPrimitive: Into + Default {} impl MarkPrimitive for bool {} impl MarkPrimitive for i32 {} impl MarkPrimitive for i64 {} impl MarkPrimitive for f64 {} impl MarkPrimitive for char {} impl MarkPrimitive for String {} -impl MarkPrimitive for Value {} +impl MarkPrimitive for JsonValue {} /// Implement CrdtNode for non-CRDTs /// This is a stub implementation so most functions don't do anything/log an error @@ -75,11 +75,11 @@ impl CrdtNode for T where T: CrdtNodeFromValue + MarkPrimitive + Hashable + Clone, { - fn apply(&mut self, _op: Op) -> OpState { + fn apply(&mut self, _op: Op) -> OpState { OpState::ErrApplyOnPrimitive } - fn view(&self) -> Value { + fn view(&self) -> JsonValue { self.to_owned().into() } @@ -113,7 +113,7 @@ pub struct SignedOp { author: AuthorId, /// Signed hash using priv key of author. Effectively [`OpID`] Use this as the ID to figure out what has been delivered already pub signed_digest: SignedDigest, - pub inner: Op, + pub inner: Op, /// List of causal dependencies pub depends_on: Vec, } @@ -245,26 +245,26 @@ impl BaseCrdt { /// An enum representing a JSON value #[derive(Clone, Debug, PartialEq)] -pub enum Value { +pub enum JsonValue { Null, Bool(bool), Number(f64), String(String), - Array(Vec), - Object(HashMap), + Array(Vec), + Object(HashMap), } -impl Display for Value { +impl Display for JsonValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { - Value::Null => "null".to_string(), - Value::Bool(b) => b.to_string(), - Value::Number(n) => n.to_string(), - Value::String(s) => format!("\"{s}\""), - Value::Array(arr) => { + 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]", @@ -283,7 +283,7 @@ impl Display for Value { ) } } - Value::Object(obj) => format!( + JsonValue::Object(obj) => format!( "{{ {} }}", obj.iter() .map(|(k, v)| format!(" \"{k}\": {v}")) @@ -295,7 +295,7 @@ impl Display for Value { } } -impl Default for Value { +impl Default for JsonValue { fn default() -> Self { Self::Null } @@ -303,17 +303,19 @@ impl Default for Value { /// 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: Value) -> Self { +impl From for serde_json::Value { + fn from(value: JsonValue) -> Self { match value { - Value::Null => serde_json::Value::Null, - Value::Bool(x) => serde_json::Value::Bool(x), - Value::Number(x) => serde_json::Value::Number(serde_json::Number::from_f64(x).unwrap()), - Value::String(x) => serde_json::Value::String(x), - Value::Array(x) => { + 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()) } - Value::Object(x) => serde_json::Value::Object( + JsonValue::Object(x) => serde_json::Value::Object( x.iter() .map(|(k, v)| (k.clone(), v.clone().into())) .collect(), @@ -322,17 +324,17 @@ impl From for serde_json::Value { } } -impl From for Value { +impl From for JsonValue { fn from(value: serde_json::Value) -> Self { match value { - serde_json::Value::Null => Value::Null, - serde_json::Value::Bool(x) => Value::Bool(x), - serde_json::Value::Number(x) => Value::Number(x.as_f64().unwrap()), - serde_json::Value::String(x) => Value::String(x), + 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) => { - Value::Array(x.iter().map(|a| a.clone().into()).collect()) + JsonValue::Array(x.iter().map(|a| a.clone().into()).collect()) } - serde_json::Value::Object(x) => Value::Object( + serde_json::Value::Object(x) => JsonValue::Object( x.iter() .map(|(k, v)| (k.clone(), v.clone().into())) .collect(), @@ -341,73 +343,73 @@ impl From for Value { } } -impl Value { +impl JsonValue { pub fn into_json(self) -> serde_json::Value { self.into() } } /// Conversions from primitive types to [`Value`] -impl From for Value { +impl From for JsonValue { fn from(val: bool) -> Self { - Value::Bool(val) + JsonValue::Bool(val) } } -impl From for Value { +impl From for JsonValue { fn from(val: i64) -> Self { - Value::Number(val as f64) + JsonValue::Number(val as f64) } } -impl From for Value { +impl From for JsonValue { fn from(val: i32) -> Self { - Value::Number(val as f64) + JsonValue::Number(val as f64) } } -impl From for Value { +impl From for JsonValue { fn from(val: f64) -> Self { - Value::Number(val) + JsonValue::Number(val) } } -impl From for Value { +impl From for JsonValue { fn from(val: String) -> Self { - Value::String(val) + JsonValue::String(val) } } -impl From for Value { +impl From for JsonValue { fn from(val: char) -> Self { - Value::String(val.into()) + JsonValue::String(val.into()) } } -impl From> for Value +impl From> for JsonValue where T: CrdtNode, { fn from(val: Option) -> Self { match val { Some(x) => x.view(), - None => Value::Null, + None => JsonValue::Null, } } } -impl From> for Value +impl From> for JsonValue where T: CrdtNode, { fn from(value: Vec) -> Self { - Value::Array(value.iter().map(|x| x.view()).collect()) + JsonValue::Array(value.iter().map(|x| x.view()).collect()) } } /// Fallibly create a CRDT Node from a JSON Value pub trait CrdtNodeFromValue: Sized { - fn node_from(value: Value, id: AuthorId, path: Vec) -> Result; + fn node_from(value: JsonValue, id: AuthorId, path: Vec) -> Result; } /// Fallibly cast a JSON Value into a CRDT Node @@ -416,7 +418,7 @@ pub trait IntoCrdtNode: Sized { } /// [`CrdtNodeFromValue`] implies [`IntoCRDTNode`] -impl IntoCrdtNode for Value +impl IntoCrdtNode for JsonValue where T: CrdtNodeFromValue, { @@ -426,16 +428,16 @@ where } /// Trivial conversion from Value to Value as CrdtNodeFromValue -impl CrdtNodeFromValue for Value { - fn node_from(value: Value, _id: AuthorId, _path: Vec) -> Result { +impl CrdtNodeFromValue for JsonValue { + fn node_from(value: JsonValue, _id: AuthorId, _path: Vec) -> Result { Ok(value) } } /// Conversions from primitives to CRDTs impl CrdtNodeFromValue for bool { - fn node_from(value: Value, _id: AuthorId, _path: Vec) -> Result { - if let Value::Bool(x) = value { + 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")) @@ -444,8 +446,8 @@ impl CrdtNodeFromValue for bool { } impl CrdtNodeFromValue for f64 { - fn node_from(value: Value, _id: AuthorId, _path: Vec) -> Result { - if let Value::Number(x) = value { + 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")) @@ -454,8 +456,8 @@ impl CrdtNodeFromValue for f64 { } impl CrdtNodeFromValue for i64 { - fn node_from(value: Value, _id: AuthorId, _path: Vec) -> Result { - if let Value::Number(x) = value { + 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")) @@ -464,8 +466,8 @@ impl CrdtNodeFromValue for i64 { } impl CrdtNodeFromValue for String { - fn node_from(value: Value, _id: AuthorId, _path: Vec) -> Result { - if let Value::String(x) = value { + 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")) @@ -474,8 +476,8 @@ impl CrdtNodeFromValue for String { } impl CrdtNodeFromValue for char { - fn node_from(value: Value, _id: AuthorId, _path: Vec) -> Result { - if let Value::String(x) = value.clone() { + 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" )) @@ -489,7 +491,7 @@ impl CrdtNodeFromValue for LwwRegisterCrdt where T: CrdtNode, { - fn node_from(value: Value, id: AuthorId, path: Vec) -> Result { + fn node_from(value: JsonValue, id: AuthorId, path: Vec) -> Result { let mut crdt = LwwRegisterCrdt::new(id, path); crdt.set(value); Ok(crdt) @@ -500,8 +502,8 @@ impl CrdtNodeFromValue for ListCrdt where T: CrdtNode, { - fn node_from(value: Value, id: AuthorId, path: Vec) -> Result { - if let Value::Array(arr) = value { + 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)| { @@ -521,7 +523,7 @@ mod test { use serde_json::json; use crate::{ - json_crdt::{add_crdt_fields, BaseCrdt, CrdtNode, IntoCrdtNode, OpState, Value}, + json_crdt::{add_crdt_fields, BaseCrdt, CrdtNode, IntoCrdtNode, JsonValue, OpState}, keypair::make_keypair, list_crdt::ListCrdt, lww_crdt::LwwRegisterCrdt, @@ -697,7 +699,7 @@ mod test { .set(3000.0) .sign_with_dependencies(&kp1, vec![&_add_money]); - let sword: Value = json!({ + let sword: JsonValue = json!({ "name": "Sword", "soulbound": true, }) @@ -748,8 +750,8 @@ mod test { let mut base2 = BaseCrdt::::new(&kp2); // init a 2d grid - let row0: Value = json!([true, false]).into(); - let row1: Value = json!([false, true]).into(); + let row0: JsonValue = json!([true, false]).into(); + let row1: JsonValue = json!([false, true]).into(); let construct1 = base1.doc.grid.insert_idx(0, row0).sign(&kp1); let construct2 = base1.doc.grid.insert_idx(1, row1).sign(&kp1); @@ -800,13 +802,13 @@ mod test { #[add_crdt_fields] #[derive(Clone, CrdtNode)] struct Test { - reg: LwwRegisterCrdt, + reg: LwwRegisterCrdt, } let kp1 = make_keypair(); let mut base1 = BaseCrdt::::new(&kp1); - let base_val: Value = json!({ + let base_val: JsonValue = json!({ "a": true, "b": "asdf", "c": { @@ -856,11 +858,11 @@ mod test { assert_eq!(crdt.doc.reg.view(), json!(true).into()); // set nested - let mut list_view: Value = crdt.doc.strct.view().into(); + let mut list_view: JsonValue = crdt.doc.strct.view().into(); assert_eq!(list_view, json!([]).into()); // only keeps actual numbers - let list: Value = json!({"list": [0, 123, -0.45, "char", []]}).into(); + let list: JsonValue = json!({"list": [0, 123, -0.45, "char", []]}).into(); crdt.doc.strct.insert_idx(0, list); list_view = crdt.doc.strct.view().into(); assert_eq!(list_view, json!([{ "list": [0, 123, -0.45]}]).into()); diff --git a/crates/bft-json-crdt/src/list_crdt.rs b/crates/bft-json-crdt/src/list_crdt.rs index 77082b4..d130f0b 100644 --- a/crates/bft-json-crdt/src/list_crdt.rs +++ b/crates/bft-json-crdt/src/list_crdt.rs @@ -1,6 +1,6 @@ use crate::{ debug::debug_path_mismatch, - json_crdt::{CrdtNode, OpState, Value}, + json_crdt::{CrdtNode, JsonValue, OpState}, keypair::AuthorId, op::*, }; @@ -48,7 +48,7 @@ where } /// Locally insert some content causally after the given operation - pub fn insert>(&mut self, after: OpId, content: U) -> Op { + pub fn insert>(&mut self, after: OpId, content: U) -> Op { let mut op = Op::new( after, self.our_id, @@ -67,7 +67,11 @@ where } /// Shorthand function to insert at index locally. Indexing ignores deleted items - pub fn insert_idx + Clone>(&mut self, idx: usize, content: U) -> Op { + pub fn insert_idx + Clone>( + &mut self, + idx: usize, + content: U, + ) -> Op { let mut i = 0; for op in &self.ops { if !op.is_deleted { @@ -97,7 +101,7 @@ where /// Mark a node as deleted. If the node doesn't exist, it will be stuck /// waiting for that node to be created. - pub fn delete(&mut self, id: OpId) -> Op { + pub fn delete(&mut self, id: OpId) -> Op { let op = Op::new( id, self.our_id, @@ -117,7 +121,7 @@ where /// Apply an operation (both local and remote) to this local list CRDT. /// Forwards it to a nested CRDT if necessary. - pub fn apply(&mut self, op: Op) -> OpState { + pub fn apply(&mut self, op: Op) -> OpState { if !op.is_valid_hash() { return OpState::ErrHashMismatch; } @@ -308,11 +312,11 @@ impl CrdtNode for ListCrdt where T: CrdtNode, { - fn apply(&mut self, op: Op) -> OpState { + fn apply(&mut self, op: Op) -> OpState { self.apply(op.into()) } - fn view(&self) -> Value { + fn view(&self) -> JsonValue { self.view().into() } diff --git a/crates/bft-json-crdt/src/lww_crdt.rs b/crates/bft-json-crdt/src/lww_crdt.rs index 5288f8b..744ef3c 100644 --- a/crates/bft-json-crdt/src/lww_crdt.rs +++ b/crates/bft-json-crdt/src/lww_crdt.rs @@ -1,5 +1,5 @@ use crate::debug::DebugView; -use crate::json_crdt::{CrdtNode, OpState, Value}; +use crate::json_crdt::{CrdtNode, OpState, JsonValue}; use crate::op::{join_path, print_path, Op, PathSegment, SequenceNumber}; use std::cmp::{max, Ordering}; use std::fmt::Debug; @@ -38,7 +38,7 @@ where } /// Sets the current value of the register - pub fn set>(&mut self, content: U) -> Op { + pub fn set>(&mut self, content: U) -> Op { let mut op = Op::new( self.value.id, self.our_id, @@ -57,7 +57,7 @@ where } /// Apply an operation (both local and remote) to this local register CRDT. - pub fn apply(&mut self, op: Op) -> OpState { + pub fn apply(&mut self, op: Op) -> OpState { if !op.is_valid_hash() { return OpState::ErrHashMismatch; } @@ -100,11 +100,11 @@ impl CrdtNode for LwwRegisterCrdt where T: CrdtNode, { - fn apply(&mut self, op: Op) -> OpState { + fn apply(&mut self, op: Op) -> OpState { self.apply(op.into()) } - fn view(&self) -> Value { + fn view(&self) -> JsonValue { self.view().into() } diff --git a/crates/bft-json-crdt/src/op.rs b/crates/bft-json-crdt/src/op.rs index 3e26f31..b217da3 100644 --- a/crates/bft-json-crdt/src/op.rs +++ b/crates/bft-json-crdt/src/op.rs @@ -1,5 +1,5 @@ use crate::debug::{debug_path_mismatch, debug_type_mismatch}; -use crate::json_crdt::{CrdtNode, CrdtNodeFromValue, IntoCrdtNode, SignedOp, Value}; +use crate::json_crdt::{CrdtNode, CrdtNodeFromValue, IntoCrdtNode, JsonValue, SignedOp}; use crate::keypair::{sha256, AuthorId}; use fastcrypto::ed25519::Ed25519KeyPair; use serde::{Deserialize, Serialize}; @@ -112,7 +112,7 @@ where } /// Conversion from Op -> Op given that T is a CRDT that can be created from a JSON value -impl Op { +impl Op { pub fn into(self) -> Op { let content = if let Some(inner_content) = self.content { match inner_content.into_node(self.id, self.path.clone()) { diff --git a/crates/bft-json-crdt/tests/commutative.rs b/crates/bft-json-crdt/tests/commutative.rs index cbe11ae..aa4294b 100644 --- a/crates/bft-json-crdt/tests/commutative.rs +++ b/crates/bft-json-crdt/tests/commutative.rs @@ -1,7 +1,7 @@ use bft_json_crdt::{ keypair::make_author, list_crdt::ListCrdt, - op::{Op, OpId, ROOT_ID}, json_crdt::{CrdtNode, Value}, + op::{Op, OpId, ROOT_ID}, json_crdt::{CrdtNode, JsonValue}, }; use rand::{rngs::ThreadRng, seq::SliceRandom, Rng}; @@ -14,9 +14,9 @@ const TEST_N: usize = 100; #[test] fn test_list_fuzz_commutative() { let mut rng = rand::thread_rng(); - let mut op_log = Vec::>::new(); - let mut op_log1 = Vec::>::new(); - let mut op_log2 = Vec::>::new(); + let mut op_log = Vec::>::new(); + let mut op_log1 = Vec::>::new(); + let mut op_log2 = Vec::>::new(); let mut l1 = ListCrdt::::new(make_author(1), vec![]); let mut l2 = ListCrdt::::new(make_author(2), vec![]); let mut chk = ListCrdt::::new(make_author(3), vec![]); @@ -62,8 +62,8 @@ fn test_list_fuzz_commutative() { assert_eq!(l2_doc, chk_doc); // now, allow cross mixing between both - let mut op_log1 = Vec::>::new(); - let mut op_log2 = Vec::>::new(); + let mut op_log1 = Vec::>::new(); + let mut op_log2 = Vec::>::new(); for _ in 0..TEST_N { let letter1: char = rng.gen_range(b'a'..=b'z') as char; let letter2: char = rng.gen_range(b'a'..=b'z') as char;