leptos_sync_core/crdt/basic/
counter.rs

1//! Counter CRDT implementations
2
3use super::{replica_id::ReplicaId, traits::{CRDT, Mergeable}};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Counter that can be incremented/decremented
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct GCounter {
10    increments: HashMap<ReplicaId, u64>,
11}
12
13impl GCounter {
14    pub fn new() -> Self {
15        Self {
16            increments: HashMap::new(),
17        }
18    }
19
20    pub fn increment(&mut self, replica_id: ReplicaId) {
21        *self.increments.entry(replica_id).or_insert(0) += 1;
22    }
23
24    pub fn value(&self) -> u64 {
25        self.increments.values().sum()
26    }
27
28    pub fn replica_value(&self, replica_id: ReplicaId) -> u64 {
29        self.increments.get(&replica_id).copied().unwrap_or(0)
30    }
31
32    pub fn len(&self) -> usize {
33        self.increments.len()
34    }
35}
36
37impl Default for GCounter {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl Mergeable for GCounter {
44    type Error = std::io::Error;
45    
46    fn merge(&mut self, other: &Self) -> Result<(), Self::Error> {
47        for (replica_id, increment) in &other.increments {
48            let current = self.increments.entry(*replica_id).or_insert(0);
49            *current = (*current).max(*increment);
50        }
51        Ok(())
52    }
53    
54    fn has_conflict(&self, _other: &Self) -> bool {
55        // G-Counters are conflict-free by design
56        false
57    }
58}
59
60impl CRDT for GCounter {
61    fn replica_id(&self) -> &ReplicaId {
62        // GCounter doesn't have a single replica ID, so we'll use a default
63        // In practice, this might need to be handled differently
64        static DEFAULT_REPLICA: std::sync::LazyLock<ReplicaId> = std::sync::LazyLock::new(|| ReplicaId::from(uuid::Uuid::nil()));
65        &DEFAULT_REPLICA
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_gcounter_creation() {
75        let counter = GCounter::new();
76        assert_eq!(counter.value(), 0);
77        assert_eq!(counter.len(), 0);
78    }
79
80    #[test]
81    fn test_gcounter_operations() {
82        let mut counter = GCounter::new();
83        let replica_id = ReplicaId::default();
84        
85        counter.increment(replica_id);
86        counter.increment(replica_id);
87        
88        assert_eq!(counter.value(), 2);
89        assert_eq!(counter.replica_value(replica_id), 2);
90    }
91
92    #[test]
93    fn test_gcounter_merge() {
94        let mut counter1 = GCounter::new();
95        let mut counter2 = GCounter::new();
96        let replica_id1 = ReplicaId::default();
97        let replica_id2 = ReplicaId::default();
98        
99        counter1.increment(replica_id1);
100        counter1.increment(replica_id1);
101        counter2.increment(replica_id2);
102        counter2.increment(replica_id2);
103        counter2.increment(replica_id2);
104        
105        counter1.merge(&counter2).unwrap();
106        
107        assert_eq!(counter1.value(), 5);
108        assert_eq!(counter1.replica_value(replica_id1), 2);
109        assert_eq!(counter1.replica_value(replica_id2), 3);
110    }
111
112    #[test]
113    fn test_gcounter_no_conflicts() {
114        let counter1 = GCounter::new();
115        let counter2 = GCounter::new();
116        
117        // G-Counters should never have conflicts
118        assert!(!counter1.has_conflict(&counter2));
119    }
120
121    #[test]
122    fn test_gcounter_serialization() {
123        let mut counter = GCounter::new();
124        let replica_id = ReplicaId::default();
125        
126        counter.increment(replica_id);
127        counter.increment(replica_id);
128        
129        let serialized = serde_json::to_string(&counter).unwrap();
130        let deserialized: GCounter = serde_json::from_str(&serialized).unwrap();
131        
132        assert_eq!(counter.value(), deserialized.value());
133        assert_eq!(counter.replica_value(replica_id), deserialized.replica_value(replica_id));
134    }
135
136    #[test]
137    fn test_gcounter_maximum_merge() {
138        let mut counter1 = GCounter::new();
139        let mut counter2 = GCounter::new();
140        let replica_id = ReplicaId::default();
141        
142        // Set different values for the same replica
143        counter1.increments.insert(replica_id, 5);
144        counter2.increments.insert(replica_id, 3);
145        
146        counter1.merge(&counter2).unwrap();
147        
148        // Should keep the maximum value
149        assert_eq!(counter1.replica_value(replica_id), 5);
150    }
151}