diff --git a/Cargo.lock b/Cargo.lock index 57961bd4..7ccabc77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2455,20 +2455,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "radicle-crdt" -version = "0.1.0" -dependencies = [ - "fastrand", - "num-traits", - "qcheck", - "qcheck-macros", - "radicle-crypto", - "serde", - "tempfile", - "thiserror 1.0.69", -] - [[package]] name = "radicle-crypto" version = "0.12.0" diff --git a/crates/radicle-crdt/Cargo.toml b/crates/radicle-crdt/Cargo.toml deleted file mode 100644 index fa99ff11..00000000 --- a/crates/radicle-crdt/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "radicle-crdt" -version = "0.1.0" -license.workspace = true -edition.workspace = true -rust-version.workspace = true - -[features] -test = ["fastrand", "qcheck"] - -[dependencies] -fastrand = { workspace = true, optional = true } -num-traits = { version = "0.2.15", default-features = false, features = ["std"] } -qcheck = { workspace = true, optional = true } -radicle-crypto = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -fastrand = { workspace = true } -qcheck = { workspace = true } -qcheck-macros = { workspace = true } -radicle-crypto = { workspace = true, features = ["test"] } -tempfile = { workspace = true } diff --git a/crates/radicle-crdt/src/clock.rs b/crates/radicle-crdt/src/clock.rs deleted file mode 100644 index 7e5699a4..00000000 --- a/crates/radicle-crdt/src/clock.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::fmt; -use std::str::FromStr; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; - -use num_traits::Bounded; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::ord::Max; -use crate::Semilattice as _; - -/// Lamport clock. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Lamport { - counter: Max, -} - -impl Lamport { - /// Return the clock value. - pub fn get(&self) -> u64 { - *self.counter.get() - } - - /// The initial value of the clock. - pub fn initial() -> Self { - Self::default() - } - - /// Increment clock and return new value. - /// Must be called before sending a message. - pub fn tick(&mut self) -> Self { - self.counter.incr(); - *self - } - - /// Merge clock with another clock, and increment value. - /// Must be called whenever a message is received. - pub fn merge(&mut self, other: Self) -> Self { - self.counter.merge(other.counter); - self.tick() - } - - /// Reset clock to default state. - pub fn reset(&mut self) { - self.counter = Max::default(); - } -} - -impl fmt::Display for Lamport { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.get()) - } -} - -impl From for Lamport { - fn from(counter: u64) -> Self { - Self { - counter: Max::from(counter), - } - } -} - -impl From for u64 { - fn from(arg: Lamport) -> Self { - arg.get() - } -} - -/// Error decoding an operation from an entry. -#[derive(Error, Debug)] -pub enum LamportError { - #[error("invalid lamport clock value")] - Invalid, -} - -impl FromStr for Lamport { - type Err = LamportError; - - fn from_str(s: &str) -> Result { - Self::try_from(s) - } -} - -impl TryFrom<&str> for Lamport { - type Error = LamportError; - - fn try_from(s: &str) -> Result { - let v = s.parse::().map_err(|_| LamportError::Invalid)?; - Ok(v.into()) - } -} - -impl Bounded for Lamport { - fn min_value() -> Self { - Self::from(u64::MIN) - } - - fn max_value() -> Self { - Self::from(u64::MAX) - } -} - -/// Physical clock. Tracks real-time by the second. -#[derive(Debug, Default, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Physical { - seconds: u64, -} - -impl Physical { - pub fn new(seconds: u64) -> Self { - Self { seconds } - } - - pub fn now() -> Self { - #[allow(clippy::unwrap_used)] // Safe because Unix was already invented! - let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - - Self { - seconds: duration.as_secs(), - } - } - - pub fn as_secs(&self) -> u64 { - self.seconds - } -} - -impl From for Physical { - fn from(seconds: u64) -> Self { - Self { seconds } - } -} - -impl std::ops::Add for Physical { - type Output = Self; - - fn add(self, rhs: u64) -> Self::Output { - Self { - seconds: self.seconds + rhs, - } - } -} - -impl Bounded for Physical { - fn min_value() -> Self { - Self { seconds: u64::MIN } - } - - fn max_value() -> Self { - Self { seconds: u64::MAX } - } -} diff --git a/crates/radicle-crdt/src/gmap.rs b/crates/radicle-crdt/src/gmap.rs deleted file mode 100644 index 9377ec00..00000000 --- a/crates/radicle-crdt/src/gmap.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::collections::btree_map::{Entry, IntoIter, IntoKeys}; -use std::collections::BTreeMap; -use std::ops::Deref; - -use crate::Semilattice; - -/// Grow-only map. -/// -/// Conflicting elements are merged via the [`Semilattice`] instance. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct GMap { - inner: BTreeMap, -} - -impl GMap { - pub fn singleton(key: K, value: V) -> Self { - Self { - inner: BTreeMap::from_iter([(key, value)]), - } - } - - pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { - self.inner.get_mut(key) - } - - pub fn insert(&mut self, key: K, value: V) { - match self.inner.entry(key) { - Entry::Occupied(mut e) => { - e.get_mut().merge(value); - } - Entry::Vacant(e) => { - e.insert(value); - } - } - } -} - -impl GMap { - pub fn into_keys(self) -> IntoKeys { - self.inner.into_keys() - } -} - -impl FromIterator<(K, V)> for GMap { - fn from_iter>(iter: I) -> Self { - let mut map = GMap::default(); - for (k, v) in iter.into_iter() { - map.insert(k, v); - } - map - } -} - -impl Extend<(K, V)> for GMap { - fn extend>(&mut self, iter: I) { - for (k, v) in iter.into_iter() { - self.insert(k, v); - } - } -} - -impl IntoIterator for GMap { - type Item = (K, V); - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.inner.into_iter() - } -} - -impl Default for GMap { - fn default() -> Self { - Self { - inner: BTreeMap::default(), - } - } -} - -impl Semilattice for GMap { - fn merge(&mut self, other: Self) { - for (k, v) in other.into_iter() { - self.insert(k, v); - } - } -} - -impl Deref for GMap { - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -#[cfg(test)] -mod tests { - use qcheck_macros::quickcheck; - - use super::*; - use crate::ord::Max; - - #[quickcheck] - fn prop_semilattice( - a: Vec<(u8, Max)>, - b: Vec<(u8, Max)>, - c: Vec<(u8, Max)>, - mix: Vec<(u8, Max)>, - ) { - let mut a = GMap::from_iter(a); - let mut b = GMap::from_iter(b); - let c = GMap::from_iter(c); - - a.extend(mix.clone()); - b.extend(mix); - - crate::test::assert_laws(&a, &b, &c); - } -} diff --git a/crates/radicle-crdt/src/gset.rs b/crates/radicle-crdt/src/gset.rs deleted file mode 100644 index 4a4c0975..00000000 --- a/crates/radicle-crdt/src/gset.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::collections::btree_map::{IntoKeys, Keys}; -use std::ops::Deref; - -use crate::GMap; -use crate::Semilattice; - -/// Grow-only set. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct GSet { - inner: GMap, -} - -impl GSet { - pub fn singleton(key: K) -> Self { - Self { - inner: GMap::from_iter([(key, ())]), - } - } - - pub fn insert(&mut self, key: K) { - self.inner.insert(key, ()); - } - - pub fn iter(&self) -> Keys<'_, K, ()> { - self.inner.keys() - } -} - -impl FromIterator for GSet { - fn from_iter>(iter: I) -> Self { - let mut map = GSet::default(); - for k in iter.into_iter() { - map.insert(k); - } - map - } -} - -impl Extend for GSet { - fn extend>(&mut self, iter: I) { - for k in iter.into_iter() { - self.insert(k); - } - } -} - -impl IntoIterator for GSet { - type Item = K; - type IntoIter = IntoKeys; - - fn into_iter(self) -> Self::IntoIter { - self.inner.into_keys() - } -} - -impl Default for GSet { - fn default() -> Self { - Self { - inner: GMap::default(), - } - } -} - -impl Semilattice for GSet { - fn merge(&mut self, other: Self) { - for k in other.into_iter() { - self.insert(k); - } - } -} - -impl Deref for GSet { - type Target = GMap; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -#[cfg(test)] -mod tests { - use qcheck_macros::quickcheck; - - use super::*; - - #[quickcheck] - fn prop_semilattice(a: Vec, b: Vec, c: Vec, mix: Vec) { - let mut a = GSet::from_iter(a); - let mut b = GSet::from_iter(b); - let c = GSet::from_iter(c); - - a.extend(mix.clone()); - b.extend(mix); - - crate::test::assert_laws(&a, &b, &c); - } -} diff --git a/crates/radicle-crdt/src/immutable.rs b/crates/radicle-crdt/src/immutable.rs deleted file mode 100644 index 252b2a1b..00000000 --- a/crates/radicle-crdt/src/immutable.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::Semilattice; - -/// A [`Semilattice`] that panics when attempting to merge inequal elements. -/// Use this for types that will never merge. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Immutable(pub T); - -impl Immutable { - /// Create a new immutable object. - pub fn new(inner: T) -> Self { - Self(inner) - } -} - -impl std::ops::Deref for Immutable { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Semilattice for Immutable { - fn merge(&mut self, other: Self) { - if self.0 != other.0 { - panic!("Immutable::merge: Cannot merge inequal objects"); - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - #[should_panic] - fn test_merge_inequal() { - let mut a = Immutable::new(0); - let b = Immutable::new(1); - - a.merge(b); - } - - #[test] - fn test_merge_equal() { - let mut a = Immutable::new(1); - let b = Immutable::new(1); - - a.merge(b); - assert_eq!(a, b); - } -} diff --git a/crates/radicle-crdt/src/lib.rs b/crates/radicle-crdt/src/lib.rs deleted file mode 100644 index b5d40df7..00000000 --- a/crates/radicle-crdt/src/lib.rs +++ /dev/null @@ -1,121 +0,0 @@ -#![allow(clippy::collapsible_if)] -#![allow(clippy::bool_assert_comparison)] -#![allow(clippy::collapsible_else_if)] -#![allow(clippy::type_complexity)] -pub mod clock; -pub mod gmap; -pub mod gset; -pub mod immutable; -pub mod lwwmap; -pub mod lwwreg; -pub mod lwwset; -pub mod ord; -pub mod redactable; - -#[cfg(any(test, feature = "test"))] -pub mod test; - -//////////////////////////////////////////////////////////////////////////////// - -pub use clock::Lamport; -pub use gmap::GMap; -pub use gset::GSet; -pub use immutable::Immutable; -pub use lwwmap::LWWMap; -pub use lwwreg::LWWReg; -pub use lwwset::LWWSet; -pub use ord::{Max, Min}; -pub use redactable::Redactable; - -//////////////////////////////////////////////////////////////////////////////// - -/// A join-semilattice. -pub trait Semilattice: Sized { - /// Merge an other semilattice into this one. - /// - /// This operation should obbey the semilattice laws and should thus be idempotent, - /// associative and commutative. - fn merge(&mut self, other: Self); - - /// Like [`Semilattice::merge`] but takes and returns a new semilattice. - fn join(mut self, other: Self) -> Self { - self.merge(other); - self - } -} - -impl Semilattice for Option { - fn merge(&mut self, other: Self) { - match (self, other) { - (this @ None, other @ Some(_)) => { - *this = other; - } - (Some(ref mut a), Some(b)) => { - a.merge(b); - } - (Some(_), None) => {} - (None, None) => {} - } - } -} - -impl Semilattice for () { - fn merge(&mut self, _other: Self) {} -} - -impl Semilattice for bool { - fn merge(&mut self, other: Self) { - match (&self, other) { - (false, true) => *self = true, - (true, false) => *self = true, - (false, false) | (true, true) => {} - } - } -} - -pub fn fold(i: impl IntoIterator) -> S -where - S: Semilattice + Default, -{ - i.into_iter().fold(S::default(), S::join) -} - -#[cfg(test)] -mod tests { - use crate::{test, Max, Min, Semilattice}; - use qcheck_macros::quickcheck; - - #[quickcheck] - fn prop_option_laws(a: Max, b: Max, c: Max) { - test::assert_laws(&a, &b, &c); - } - - #[quickcheck] - fn prop_bool_laws(a: bool, b: bool, c: bool) { - test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_bool() { - assert_eq!(false.join(false), false); - assert_eq!(true.join(true), true); - assert_eq!(true.join(false), true); - assert_eq!(false.join(true), true); - } - - #[test] - fn test_option() { - assert_eq!(None::<()>.join(None), None); - assert_eq!(None::<()>.join(Some(())), Some(())); - assert_eq!(Some(()).join(None), Some(())); - assert_eq!(Some(()).join(Some(())), Some(())); - assert_eq!( - Some(Max::from(0)).join(Some(Max::from(1))), - Some(Max::from(1)) - ); - assert_eq!( - Some(Min::from(0)).join(Some(Min::from(1))), - Some(Min::from(0)) - ); - } -} diff --git a/crates/radicle-crdt/src/lwwmap.rs b/crates/radicle-crdt/src/lwwmap.rs deleted file mode 100644 index 19a92929..00000000 --- a/crates/radicle-crdt/src/lwwmap.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::gmap::GMap; -use crate::lwwreg::LWWReg; -use crate::{clock, Semilattice}; - -/// Last-Write-Wins Map. -/// -/// In case a value is added and removed under a key at the same time, -/// the "add" takes precedence over the "remove". -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LWWMap { - inner: GMap, C>>, -} - -impl LWWMap { - pub fn singleton(key: K, value: V, clock: C) -> Self { - Self { - inner: GMap::singleton(key, LWWReg::new(Some(value), clock)), - } - } - - pub fn get(&self, key: &K) -> Option<&V> { - let Some(value) = self.inner.get(key) else { - // If the element was never added, return nothing. - return None; - }; - value.get().as_ref() - } - - pub fn insert(&mut self, key: K, value: V, clock: C) { - self.inner.insert(key, LWWReg::new(Some(value), clock)); - } - - pub fn remove(&mut self, key: K, clock: C) { - self.inner.insert(key, LWWReg::new(None, clock)); - } - - pub fn contains_key(&self, key: &K) -> bool { - let Some(value) = self.inner.get(key) else { - // If the element was never added, return false. - return false; - }; - value.get().is_some() - } - - pub fn iter(&self) -> impl Iterator { - self.inner - .iter() - .filter_map(|(k, v)| v.get().as_ref().map(|v| (k, v))) - } - - pub fn len(&self) -> usize { - self.iter().count() - } - - pub fn is_empty(&self) -> bool { - self.iter().next().is_none() - } -} - -impl Default for LWWMap { - fn default() -> Self { - Self { - inner: GMap::default(), - } - } -} - -impl FromIterator<(K, V, C)> for LWWMap { - fn from_iter>(iter: I) -> Self { - let mut map = LWWMap::default(); - for (k, v, c) in iter.into_iter() { - map.insert(k, v, c); - } - map - } -} - -impl Extend<(K, V, C)> for LWWMap { - fn extend>(&mut self, iter: I) { - for (k, v, c) in iter.into_iter() { - self.insert(k, v, c); - } - } -} - -impl Semilattice for LWWMap -where - K: Ord, - V: Semilattice, - C: Ord, -{ - fn merge(&mut self, other: Self) { - self.inner.merge(other.inner); - } -} - -#[cfg(test)] -mod tests { - use qcheck_macros::quickcheck; - - use super::*; - use crate::ord::Max; - - #[quickcheck] - fn prop_semilattice( - a: Vec<(u8, Max, u16)>, - b: Vec<(u8, Max, u16)>, - c: Vec<(u8, Max, u16)>, - mix: Vec<(u8, Max, u16)>, - ) { - let mut a = LWWMap::from_iter(a); - let mut b = LWWMap::from_iter(b); - let c = LWWMap::from_iter(c); - - a.extend(mix.clone()); - b.extend(mix); - - crate::test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_insert() { - let mut map = LWWMap::default(); - - map.insert('a', Max::from(1), 0); - map.insert('b', Max::from(2), 0); - map.insert('c', Max::from(3), 0); - - assert_eq!(map.get(&'a'), Some(&Max::from(1))); - assert_eq!(map.get(&'b'), Some(&Max::from(2))); - assert_eq!(map.get(&'?'), None); - - let values = map.iter().collect::)>>(); - assert!(values.contains(&(&'a', &Max::from(1)))); - assert!(values.contains(&(&'b', &Max::from(2)))); - assert!(values.contains(&(&'c', &Max::from(3)))); - assert_eq!(values.len(), 3); - } - - #[test] - fn test_insert_remove() { - let mut map = LWWMap::default(); - - map.insert('a', Max::from("alice"), 1); - assert!(map.contains_key(&'a')); - - map.remove('a', 0); - assert!(map.contains_key(&'a')); - - map.remove('a', 1); - assert!(map.contains_key(&'a')); // Add takes precedence over remove. - assert!(map.iter().any(|(c, _)| *c == 'a')); - - map.remove('a', 2); - assert!(!map.contains_key(&'a')); - assert!(!map.iter().any(|(c, _)| *c == 'a')); - } - - #[test] - fn test_is_empty() { - let mut map = LWWMap::default(); - assert!(map.is_empty()); - - map.insert('a', Max::from("alice"), 1); - assert!(!map.is_empty()); - - map.remove('a', 2); - assert!(map.is_empty()); - } - - #[test] - fn test_remove_insert() { - let mut map = LWWMap::default(); - - map.insert('a', Max::from("alice"), 1); - assert_eq!(map.get(&'a'), Some(&Max::from("alice"))); - - map.remove('a', 2); - assert!(!map.contains_key(&'a')); - - map.insert('a', Max::from("alice"), 1); - assert!(!map.contains_key(&'a')); - - map.insert('a', Max::from("amy"), 2); - assert_eq!(map.get(&'a'), Some(&Max::from("amy"))); - } -} diff --git a/crates/radicle-crdt/src/lwwreg.rs b/crates/radicle-crdt/src/lwwreg.rs deleted file mode 100644 index 9c88701f..00000000 --- a/crates/radicle-crdt/src/lwwreg.rs +++ /dev/null @@ -1,134 +0,0 @@ -use num_traits::Bounded; - -use crate::clock; -use crate::ord::Max; -use crate::Semilattice; - -/// Last-Write-Wins Register. -/// -/// In case of conflict, uses the [`Semilattice`] instance of `T` to merge. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LWWReg { - clock: Max, - value: T, -} - -impl LWWReg { - pub fn initial(value: T) -> Self - where - C: Default, - { - Self { - clock: Max::from(C::default()), - value, - } - } - - pub fn new(value: T, clock: C) -> Self { - Self { - clock: Max::from(clock), - value, - } - } - - pub fn set(&mut self, value: impl Into, clock: C) { - let clock = Max::from(clock); - let value = value.into(); - - if clock == self.clock { - self.value.merge(value); - } else if clock > self.clock { - self.clock.merge(clock); - self.value = value; - } - } - - pub fn get(&self) -> &T { - &self.value - } - - pub fn clock(&self) -> &Max { - &self.clock - } - - pub fn into_inner(self) -> (T, C) { - (self.value, self.clock.into_inner()) - } -} - -impl Default for LWWReg { - fn default() -> Self { - Self { - clock: Max::default(), - value: T::default(), - } - } -} - -impl Semilattice for LWWReg -where - T: Semilattice, - C: PartialOrd, -{ - fn merge(&mut self, other: Self) { - self.set(other.value, other.clock.into_inner()); - } -} - -#[cfg(test)] -mod tests { - use qcheck_macros::quickcheck; - - use super::*; - use crate::Min; - - #[quickcheck] - fn prop_semilattice(a: (Max, u16), b: (Max, u16), c: (Max, u16)) { - let a = LWWReg::new(a.0, a.1); - let b = LWWReg::new(b.0, b.1); - let c = LWWReg::new(c.0, c.1); - - crate::test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_merge() { - let a = LWWReg::new(Max::from(0), 0); - let b = LWWReg::new(Max::from(1), 0); - - assert_eq!(a.join(b).get(), &Max::from(1)); - - let a = LWWReg::new(Min::from(0), 0); - let b = LWWReg::new(Min::from(1), 0); - - assert_eq!(a.join(b).get(), &Min::from(0)); - } - - #[test] - fn test_set_get() { - let mut reg = LWWReg::new(Max::from(42), 1); - assert_eq!(*reg.get(), Max::from(42)); - - reg.set(84, 0); - assert_eq!(*reg.get(), Max::from(42)); - - reg.set(84, 2); - assert_eq!(*reg.get(), Max::from(84)); - - // Smaller value, same clock: smaller value loses. - reg.set(42, 2); - assert_eq!(*reg.get(), Max::from(84)); - - // Bigger value, same clock: bigger value wins. - reg.set(168, 2); - assert_eq!(*reg.get(), Max::from(168)); - - // Smaller value, newer clock: smaller value wins. - reg.set(42, 3); - assert_eq!(*reg.get(), Max::from(42)); - - // Same value, newer clock: newer clock is set. - reg.set(42, 4); - assert_eq!(*reg.clock(), Max::from(4)); - } -} diff --git a/crates/radicle-crdt/src/lwwset.rs b/crates/radicle-crdt/src/lwwset.rs deleted file mode 100644 index 06a9b883..00000000 --- a/crates/radicle-crdt/src/lwwset.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::clock; -use crate::{lwwmap::LWWMap, Semilattice}; - -/// Last-Write-Wins Set. -/// -/// In case the same value is added and removed at the same time, -/// the "add" takes precedence over the "remove". -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LWWSet { - inner: LWWMap, -} - -impl LWWSet { - pub fn singleton(value: T, clock: C) -> Self { - Self { - inner: LWWMap::from_iter([(value, (), clock)]), - } - } - - pub fn insert(&mut self, value: T, clock: C) { - self.inner.insert(value, (), clock); - } - - pub fn remove(&mut self, value: T, clock: C) { - self.inner.remove(value, clock); - } - - pub fn contains(&self, value: &T) -> bool { - self.inner.contains_key(value) - } - - pub fn iter(&self) -> impl Iterator { - self.inner.iter().map(|(k, _)| k) - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl Default for LWWSet { - fn default() -> Self { - Self { - inner: LWWMap::default(), - } - } -} - -impl FromIterator<(T, C)> for LWWSet { - fn from_iter>(iter: I) -> Self { - let mut set = LWWSet::default(); - for (v, c) in iter.into_iter() { - set.insert(v, c); - } - set - } -} - -impl Extend<(T, C)> for LWWSet { - fn extend>(&mut self, iter: I) { - for (v, c) in iter.into_iter() { - self.insert(v, c); - } - } -} - -impl Semilattice for LWWSet -where - T: Ord, - C: Ord + Default, -{ - fn merge(&mut self, other: Self) { - self.inner.merge(other.inner); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use qcheck_macros::quickcheck; - - #[quickcheck] - fn prop_semilattice( - a: Vec<(u8, u16)>, - b: Vec<(u8, u16)>, - c: Vec<(u8, u16)>, - mix: Vec<(u8, u16)>, - ) { - let mut a = LWWSet::from_iter(a); - let mut b = LWWSet::from_iter(b); - let c = LWWSet::from_iter(c); - - a.extend(mix.clone()); - b.extend(mix); - - crate::test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_insert() { - let mut set = LWWSet::default(); - - set.insert('a', 0); - set.insert('b', 0); - set.insert('c', 0); - - assert!(set.contains(&'a')); - assert!(set.contains(&'b')); - assert!(!set.contains(&'?')); - - let values = set.iter().cloned().collect::>(); - assert!(values.contains(&'a')); - assert!(values.contains(&'b')); - assert!(values.contains(&'c')); - assert_eq!(values.len(), 3); - } - - #[test] - fn test_insert_remove() { - let mut set = LWWSet::default(); - - set.insert('a', 1); - assert!(set.contains(&'a')); - - set.remove('a', 0); - assert!(set.contains(&'a')); - - set.remove('a', 1); - assert!(set.contains(&'a')); // Add takes precedence over remove. - assert!(set.iter().any(|c| *c == 'a')); - - set.remove('a', 2); - assert!(!set.contains(&'a')); - assert!(!set.iter().any(|c| *c == 'a')); - - set.insert('b', 3); - set.remove('b', 3); - assert!(set.contains(&'b')); // Insert precedence. - - set.remove('c', 3); - set.insert('c', 3); - assert!(set.contains(&'c')); // Insert precedence. - } - - #[test] - fn test_remove_insert() { - let mut set = LWWSet::default(); - - set.insert('a', 1); - assert!(set.contains(&'a')); - - set.remove('a', 2); - assert!(!set.contains(&'a')); - - set.insert('a', 1); - assert!(!set.contains(&'a')); - - set.insert('a', 2); - assert!(set.contains(&'a')); - } -} diff --git a/crates/radicle-crdt/src/ord.rs b/crates/radicle-crdt/src/ord.rs deleted file mode 100644 index 056445a2..00000000 --- a/crates/radicle-crdt/src/ord.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::{cmp, ops}; - -use num_traits::Bounded; -use serde::{Deserialize, Serialize}; - -use crate::Semilattice; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Max(T); - -impl Max { - pub fn get(&self) -> &T { - &self.0 - } - - pub fn into_inner(self) -> T { - self.0 - } -} - -impl Max { - pub fn incr(&mut self) { - self.0 = self.0.saturating_add(&T::one()); - } -} - -impl Default for Max -where - T: Bounded, -{ - fn default() -> Self { - Self(T::min_value()) - } -} - -impl ops::Deref for Max { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for Max { - fn from(t: T) -> Self { - Self(t) - } -} - -impl Semilattice for Max { - fn merge(&mut self, other: Self) { - if other.0 > self.0 { - self.0 = other.0; - } - } -} - -impl Bounded for Max { - fn min_value() -> Self { - Self::from(T::min_value()) - } - - fn max_value() -> Self { - Self::from(T::max_value()) - } -} - -#[allow(clippy::derive_ord_xor_partial_ord)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Min(pub T); - -impl Default for Min -where - T: Bounded, -{ - fn default() -> Self { - Self(T::max_value()) - } -} - -impl ops::Deref for Min { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for Min { - fn from(t: T) -> Self { - Self(t) - } -} - -impl cmp::PartialOrd for Min -where - T: PartialOrd, -{ - fn partial_cmp(&self, other: &Self) -> Option { - other.0.partial_cmp(&self.0) - } -} - -impl Semilattice for Min { - fn merge(&mut self, other: Self) { - if other.0 < self.0 { - self.0 = other.0; - } - } -} - -#[cfg(any(test, feature = "test"))] -mod arbitrary { - use super::*; - - impl qcheck::Arbitrary for Max { - fn arbitrary(g: &mut qcheck::Gen) -> Self { - Self::from(T::arbitrary(g)) - } - } - - impl qcheck::Arbitrary for Min { - fn arbitrary(g: &mut qcheck::Gen) -> Self { - Self::from(T::arbitrary(g)) - } - } -} diff --git a/crates/radicle-crdt/src/redactable.rs b/crates/radicle-crdt/src/redactable.rs deleted file mode 100644 index 6061f49a..00000000 --- a/crates/radicle-crdt/src/redactable.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::Semilattice; - -/// An object that can be either present or removed. -/// -/// The "redacted" state is the top-most element and takes precedence -/// over other states. -/// -/// There is no `Default` instance, since this is not a "bounded" semilattice. -/// -/// Nb. The merge rules are such that if two redactables with different -/// values present are merged; the result is redacted. This is the preserve -/// the semilattice laws. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Redactable { - /// When the object is present. - Present(T), - /// When the object has been removed. - Redacted, -} - -impl Redactable { - pub fn get(&self) -> Option<&T> { - match self { - Self::Present(val) => Some(val), - Self::Redacted => None, - } - } - - pub fn get_mut(&mut self) -> Option<&mut T> { - match self { - Self::Present(ref mut val) => Some(val), - Self::Redacted => None, - } - } -} - -impl From> for Redactable { - fn from(option: Option) -> Self { - match option { - Some(v) => Self::Present(v), - None => Self::Redacted, - } - } -} - -impl From> for Option { - fn from(redactable: Redactable) -> Self { - match redactable { - Redactable::Present(v) => Some(v), - Redactable::Redacted => None, - } - } -} - -impl<'a, T> From<&'a Redactable> for Option<&'a T> { - fn from(redactable: &'a Redactable) -> Self { - redactable.get() - } -} - -impl Semilattice for Redactable { - fn merge(&mut self, other: Self) { - match (&self, other) { - (Self::Redacted, _) => {} - (Self::Present(_), Self::Redacted) => { - *self = Self::Redacted; - } - (Self::Present(a), Self::Present(b)) => { - if a != &b { - *self = Self::Redacted; - } - } - } - } -} - -#[cfg(test)] -mod test { - use qcheck_macros::quickcheck; - - use super::*; - use crate::test; - - #[quickcheck] - fn prop_invariants(a: Option, b: Option, c: Option) { - let a = Redactable::from(a); - let b = Redactable::from(b); - let c = Redactable::from(c); - - test::assert_laws(&a, &b, &c); - } - - #[test] - fn test_redacted() { - let a = Redactable::Present(0); - let b = Redactable::Redacted; - - assert_eq!(a.join(b), Redactable::Redacted); - assert_eq!(b.join(a), Redactable::Redacted); - assert_eq!(a.join(a), a); - } - - #[test] - fn test_both_present() { - let a = Redactable::Present(0); - let b = Redactable::Present(1); - - assert_eq!(a.join(b), Redactable::Redacted); - assert_eq!(a.join(b), b.join(a)); - } -} diff --git a/crates/radicle-crdt/src/test.rs b/crates/radicle-crdt/src/test.rs deleted file mode 100644 index e07d708b..00000000 --- a/crates/radicle-crdt/src/test.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::fmt::Debug; -use std::rc::Rc; - -use super::*; - -/// Generate test values following a weight distribution. -pub struct WeightedGenerator<'a, T, C> { - cases: Vec Option + 'a>>, - rng: fastrand::Rng, - ctx: C, -} - -impl Iterator for WeightedGenerator<'_, T, C> { - type Item = T; - - fn next(&mut self) -> Option { - let cases = self.cases.len(); - - loop { - let r = self.rng.usize(0..cases); - let g = &self.cases[r]; - - if let Some(val) = g(&mut self.ctx, self.rng.clone()) { - return Some(val); - } - } - } -} - -impl<'a, T, C: Default> WeightedGenerator<'a, T, C> { - /// Create a new distribution. - pub fn new(rng: fastrand::Rng) -> Self { - Self { - cases: Vec::new(), - rng, - ctx: C::default(), - } - } - - /// Add a new variant with a given weight and generator function. - pub fn variant( - mut self, - weight: usize, - generator: impl Fn(&mut C, fastrand::Rng) -> Option + 'a, - ) -> Self { - let gen = Rc::new(generator); - for _ in 0..weight { - self.cases.push(gen.clone()); - } - self - } -} - -/// Assert semilattice ACI laws. -pub fn assert_laws(a: &S, b: &S, c: &S) { - assert_associative(a, b, c); - assert_commutative(a, b); - assert_commutative(b, c); - assert_idempotent(a); - assert_idempotent(b); - assert_idempotent(c); -} - -pub fn assert_associative(a: &S, b: &S, c: &S) { - // (a ^ b) ^ c - let s1 = a.clone().join(b.clone()).join(c.clone()); - // a ^ (b ^ c) - let s2 = a.clone().join(b.clone().join(c.clone())); - // (a ^ b) ^ c = a ^ (b ^ c) - assert_eq!(s1, s2, "associativity"); -} - -pub fn assert_commutative(a: &S, b: &S) { - // a ^ b - let s1 = a.clone().join(b.clone()); - // b ^ a - let s2 = b.clone().join(a.clone()); - // a ^ b = b ^ a - assert_eq!(s1, s2, "commutativity"); -} - -pub fn assert_idempotent(a: &S) { - // a ^ a - let s1 = a.clone().join(a.clone()); - // a - let s2 = a.clone(); - // a ^ a = a - assert_eq!(s1, s2, "idempotence"); -} - -#[test] -fn test_generator() { - let rng = fastrand::Rng::with_seed(0); - let dist = WeightedGenerator::::new(rng) - .variant(1, |_, _| Some('a')) - .variant(2, |_, _| Some('b')) - .variant(4, |_, _| Some('c')) - .variant(8, |_, _| Some('d')); - - let values = dist.take(1000).collect::>(); - - let a = values.iter().filter(|c| **c == 'a').count(); - let b = values.iter().filter(|c| **c == 'b').count(); - let c = values.iter().filter(|c| **c == 'c').count(); - let d = values.iter().filter(|c| **c == 'd').count(); - - assert_eq!(a, 63); - assert_eq!(b, 151); - assert_eq!(c, 255); - assert_eq!(d, 531); -}