use uni_plugin::PluginRegistry;
use uni_plugin::traits::crdt::{CrdtKind, CrdtOp};
use crate::{Crdt, CrdtError};
impl Crdt {
fn ensure_same_kind(&self, other: &Self) -> Result<(), CrdtError> {
if std::mem::discriminant(self) == std::mem::discriminant(other) {
Ok(())
} else {
Err(CrdtError::TypeMismatch(
self.type_name().to_owned(),
other.type_name().to_owned(),
))
}
}
#[must_use]
pub fn kind(&self) -> CrdtKind {
macro_rules! kind_arms {
($($variant:ident => $type_name:literal => $kind:literal,)*) => {
match self {
$(
Crdt::$variant(_) => $kind,
)*
}
};
}
CrdtKind::new(crate::for_each_crdt_variant!(kind_arms))
}
pub fn merge_via_registry(
&mut self,
other: &Self,
registry: &PluginRegistry,
) -> Result<(), CrdtError> {
self.ensure_same_kind(other)?;
let kind = self.kind();
let Some(provider) = registry.crdt_kind(&kind) else {
return self.try_merge(other);
};
let self_bytes = self.to_msgpack()?;
let other_bytes = other.to_msgpack()?;
let mut lhs = provider
.from_persisted(&self_bytes)
.map_err(|e| CrdtError::Serialization(format!("{kind:?} from_persisted (lhs): {e}")))?;
let rhs = provider
.from_persisted(&other_bytes)
.map_err(|e| CrdtError::Serialization(format!("{kind:?} from_persisted (rhs): {e}")))?;
lhs.merge(rhs.as_ref())
.map_err(|e| CrdtError::Serialization(format!("{kind:?} merge: {e}")))?;
let merged = lhs
.persist()
.map_err(|e| CrdtError::Serialization(format!("{kind:?} persist: {e}")))?;
*self = Crdt::from_msgpack(&merged)?;
Ok(())
}
}
#[must_use]
pub fn op_from_bytes(bytes: Vec<u8>) -> CrdtOp {
CrdtOp { bytes }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::GCounter;
#[test]
fn kind_maps_each_variant_to_a_distinct_string() {
let mut seen = std::collections::HashSet::new();
let crdts = [
Crdt::GCounter(GCounter::new()),
Crdt::GSet(crate::GSet::new()),
Crdt::ORSet(crate::ORSet::new()),
Crdt::LWWRegister(crate::LWWRegister::new(serde_json::Value::Null, 0)),
];
for c in &crdts {
assert!(
seen.insert(c.kind().0.to_string()),
"kind not unique: {c:?}"
);
}
}
#[test]
fn merge_via_registry_falls_back_to_native_when_no_provider() {
let registry = PluginRegistry::new();
let mut a = GCounter::new();
a.increment("r1", 5);
let mut b = GCounter::new();
b.increment("r2", 3);
let mut x = Crdt::GCounter(a);
let y = Crdt::GCounter(b);
x.merge_via_registry(&y, ®istry).unwrap();
match x {
Crdt::GCounter(c) => assert_eq!(c.value(), 8),
other => panic!("expected GCounter, got {other:?}"),
}
}
}