leptos_sync_core/crdt/basic/
lww_map.rs

1//! Last-Write-Wins Map implementation
2
3use super::{replica_id::ReplicaId, traits::{CRDT, Mergeable}, lww_register::LwwRegister};
4use std::collections::HashMap;
5use std::hash::Hash;
6
7/// Last-Write-Wins Map
8#[derive(Debug, Clone)]
9pub struct LwwMap<K, V> {
10    data: HashMap<K, LwwRegister<V>>,
11}
12
13impl<K, V> LwwMap<K, V>
14where
15    K: Clone + Eq + Hash + Send + Sync,
16    V: Clone + PartialEq + Send + Sync,
17{
18    pub fn new() -> Self {
19        Self {
20            data: HashMap::new(),
21        }
22    }
23
24    pub fn insert(&mut self, key: K, value: V, replica_id: ReplicaId) {
25        let register = LwwRegister::new(value, replica_id);
26        self.data.insert(key, register);
27    }
28
29    pub fn get(&self, key: &K) -> Option<&V> {
30        self.data.get(key).map(|register| register.value())
31    }
32
33    pub fn get_register(&self, key: &K) -> Option<&LwwRegister<V>> {
34        self.data.get(key)
35    }
36
37    pub fn remove(&mut self, key: &K) -> Option<V> {
38        self.data.remove(key).map(|register| register.value().clone())
39    }
40
41    pub fn contains_key(&self, key: &K) -> bool {
42        self.data.contains_key(key)
43    }
44
45    pub fn len(&self) -> usize {
46        self.data.len()
47    }
48
49    pub fn is_empty(&self) -> bool {
50        self.data.is_empty()
51    }
52
53    pub fn keys(&self) -> impl Iterator<Item = &K> {
54        self.data.keys()
55    }
56
57    pub fn values(&self) -> impl Iterator<Item = &V> {
58        self.data.values().map(|register| register.value())
59    }
60
61    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
62        self.data.iter().map(|(k, v)| (k, v.value()))
63    }
64}
65
66impl<K, V> Default for LwwMap<K, V>
67where
68    K: Clone + Eq + Hash + Send + Sync,
69    V: Clone + PartialEq + Send + Sync,
70{
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76impl<K, V> Mergeable for LwwMap<K, V>
77where
78    K: Clone + Eq + Hash + Send + Sync,
79    V: Clone + PartialEq + Send + Sync,
80{
81    type Error = std::io::Error;
82    
83    fn merge(&mut self, other: &Self) -> Result<(), Self::Error> {
84        for (key, other_register) in &other.data {
85            match self.data.get_mut(key) {
86                Some(existing_register) => {
87                    existing_register.merge(other_register)?;
88                }
89                None => {
90                    self.data.insert(key.clone(), other_register.clone());
91                }
92            }
93        }
94        Ok(())
95    }
96    
97    fn has_conflict(&self, other: &Self) -> bool {
98        for (key, other_register) in &other.data {
99            if let Some(existing_register) = self.data.get(key) {
100                if existing_register.has_conflict(other_register) {
101                    return true;
102                }
103            }
104        }
105        false
106    }
107}
108
109impl<K, V> CRDT for LwwMap<K, V> {
110    fn replica_id(&self) -> &ReplicaId {
111        // LwwMap doesn't have a single replica ID, so we'll use a default
112        // In practice, this might need to be handled differently
113        static DEFAULT_REPLICA: std::sync::LazyLock<ReplicaId> = std::sync::LazyLock::new(|| ReplicaId::from(uuid::Uuid::nil()));
114        &DEFAULT_REPLICA
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_lww_map_creation() {
124        let map: LwwMap<String, String> = LwwMap::new();
125        assert!(map.is_empty());
126        assert_eq!(map.len(), 0);
127    }
128
129    #[test]
130    fn test_lww_map_operations() {
131        let mut map = LwwMap::new();
132        let replica_id = ReplicaId::default();
133        
134        map.insert("key1".to_string(), "value1".to_string(), replica_id);
135        assert_eq!(map.get(&"key1".to_string()), Some(&"value1".to_string()));
136        assert_eq!(map.len(), 1);
137        
138        map.remove(&"key1".to_string());
139        assert_eq!(map.len(), 0);
140    }
141
142    #[test]
143    fn test_lww_map_merge() {
144        let mut map1 = LwwMap::new();
145        let mut map2 = LwwMap::new();
146        let replica_id1 = ReplicaId::default();
147        let replica_id2 = ReplicaId::default();
148        
149        map1.insert("key1".to_string(), "value1".to_string(), replica_id1);
150        map2.insert("key2".to_string(), "value2".to_string(), replica_id2);
151        
152        map1.merge(&map2).unwrap();
153        
154        assert_eq!(map1.len(), 2);
155        assert_eq!(map1.get(&"key1".to_string()), Some(&"value1".to_string()));
156        assert_eq!(map1.get(&"key2".to_string()), Some(&"value2".to_string()));
157    }
158
159    #[test]
160    fn test_lww_map_iteration() {
161        let mut map = LwwMap::new();
162        let replica_id = ReplicaId::default();
163        
164        map.insert("key1".to_string(), "value1".to_string(), replica_id);
165        map.insert("key2".to_string(), "value2".to_string(), replica_id);
166        
167        let mut keys: Vec<_> = map.keys().collect();
168        keys.sort();
169        assert_eq!(keys, vec![&"key1".to_string(), &"key2".to_string()]);
170        
171        let mut values: Vec<_> = map.values().collect();
172        values.sort();
173        assert_eq!(values, vec![&"value1".to_string(), &"value2".to_string()]);
174    }
175
176    #[test]
177    fn test_lww_map_conflict_detection() {
178        let mut map1 = LwwMap::new();
179        let mut map2 = LwwMap::new();
180        let replica_id1 = ReplicaId::default();
181        let replica_id2 = ReplicaId::default();
182        
183        // Create conflicting entries with same timestamp
184        let timestamp = chrono::Utc::now();
185        let mut reg1 = LwwRegister::new("value1", replica_id1).with_timestamp(timestamp);
186        let reg2 = LwwRegister::new("value2", replica_id2).with_timestamp(timestamp);
187        
188        map1.data.insert("key1".to_string(), reg1);
189        map2.data.insert("key1".to_string(), reg2);
190        
191        assert!(map1.has_conflict(&map2));
192    }
193}