tirea_state/lattice/
registry.rs

1//! Type-erased lattice merge registry.
2//!
3//! `LatticeRegistry` maps paths to type-erased merge functions, enabling
4//! `apply_patch_with_registry` to perform proper lattice merges at runtime.
5
6use crate::{Lattice, Path, TireaError, TireaResult};
7use serde::de::DeserializeOwned;
8use serde::Serialize;
9use serde_json::Value;
10use std::collections::HashMap;
11use std::marker::PhantomData;
12use std::sync::Arc;
13
14/// Type-erased lattice merge function.
15pub trait LatticeMerger: Send + Sync {
16    /// Merge a delta into the current value.
17    ///
18    /// If `current` is `None` (field missing), the delta is returned directly.
19    /// Otherwise, deserializes both, calls `Lattice::merge`, and serializes the result.
20    fn merge(&self, current: Option<&Value>, delta: &Value) -> TireaResult<Value>;
21}
22
23/// Blanket `LatticeMerger` implementation for any `Lattice + Serialize + DeserializeOwned` type.
24struct LatticeAdapter<T>(PhantomData<T>);
25
26impl<T> LatticeMerger for LatticeAdapter<T>
27where
28    T: Lattice + Serialize + DeserializeOwned + Send + Sync,
29{
30    fn merge(&self, current: Option<&Value>, delta: &Value) -> TireaResult<Value> {
31        let d: T = serde_json::from_value(delta.clone()).map_err(TireaError::from)?;
32        let merged = match current {
33            None => d,
34            Some(v) => {
35                let c: T = serde_json::from_value(v.clone()).map_err(TireaError::from)?;
36                Lattice::merge(&c, &d)
37            }
38        };
39        serde_json::to_value(&merged).map_err(TireaError::from)
40    }
41}
42
43/// Registry that maps paths to type-erased lattice merge functions.
44pub struct LatticeRegistry {
45    entries: HashMap<Path, Arc<dyn LatticeMerger>>,
46}
47
48impl LatticeRegistry {
49    /// Create an empty registry.
50    pub fn new() -> Self {
51        Self {
52            entries: HashMap::new(),
53        }
54    }
55
56    /// Register a lattice type at the given path.
57    pub fn register<T>(&mut self, path: impl Into<Path>)
58    where
59        T: Lattice + Serialize + DeserializeOwned + Send + Sync + 'static,
60    {
61        self.entries
62            .insert(path.into(), Arc::new(LatticeAdapter::<T>(PhantomData)));
63    }
64
65    /// Look up the merger for a path.
66    pub fn get(&self, path: &Path) -> Option<&dyn LatticeMerger> {
67        self.entries.get(path).map(|arc| arc.as_ref())
68    }
69}
70
71impl Default for LatticeRegistry {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::{path, GCounter};
81
82    #[test]
83    fn test_register_and_merge() {
84        let mut registry = LatticeRegistry::new();
85        registry.register::<GCounter>(path!("counter"));
86
87        let merger = registry.get(&path!("counter")).unwrap();
88
89        // Merge into None (missing field)
90        let delta = serde_json::to_value(GCounter::new()).unwrap();
91        let result = merger.merge(None, &delta).unwrap();
92        assert_eq!(result, delta);
93    }
94
95    #[test]
96    fn test_merge_two_counters() {
97        let mut registry = LatticeRegistry::new();
98        registry.register::<GCounter>(path!("counter"));
99
100        let mut c1 = GCounter::new();
101        c1.increment("n1", 5);
102        let mut c2 = GCounter::new();
103        c2.increment("n2", 3);
104
105        let v1 = serde_json::to_value(&c1).unwrap();
106        let v2 = serde_json::to_value(&c2).unwrap();
107
108        let merger = registry.get(&path!("counter")).unwrap();
109        let merged = merger.merge(Some(&v1), &v2).unwrap();
110
111        let result: GCounter = serde_json::from_value(merged).unwrap();
112        assert_eq!(result.value(), 8);
113    }
114
115    #[test]
116    fn test_unregistered_path_returns_none() {
117        let registry = LatticeRegistry::new();
118        assert!(registry.get(&path!("missing")).is_none());
119    }
120}