Skip to main content

crdt_kit/
pncounter.rs

1use alloc::string::String;
2
3use crate::{Crdt, DeltaCrdt, GCounter, GCounterDelta};
4
5/// A positive-negative counter (PN-Counter).
6///
7/// Supports both increment and decrement operations by maintaining two
8/// internal G-Counters: one for increments and one for decrements.
9/// The value is `increments - decrements`.
10///
11/// # Example
12///
13/// ```
14/// use crdt_kit::prelude::*;
15///
16/// let mut c1 = PNCounter::new("node-1");
17/// c1.increment();
18/// c1.increment();
19/// c1.decrement();
20/// assert_eq!(c1.value(), 1);
21///
22/// let mut c2 = PNCounter::new("node-2");
23/// c2.decrement();
24///
25/// c1.merge(&c2);
26/// assert_eq!(c1.value(), 0);
27/// ```
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct PNCounter {
31    increments: GCounter,
32    decrements: GCounter,
33}
34
35impl PNCounter {
36    /// Create a new PN-Counter for the given actor/replica ID.
37    pub fn new(actor: impl Into<String>) -> Self {
38        let actor = actor.into();
39        Self {
40            increments: GCounter::new(actor.clone()),
41            decrements: GCounter::new(actor),
42        }
43    }
44
45    /// Increment the counter by 1.
46    pub fn increment(&mut self) {
47        self.increments.increment();
48    }
49
50    /// Decrement the counter by 1.
51    pub fn decrement(&mut self) {
52        self.decrements.increment();
53    }
54
55    /// Get the current counter value (increments - decrements).
56    #[must_use]
57    pub fn value(&self) -> i64 {
58        self.increments.value() as i64 - self.decrements.value() as i64
59    }
60}
61
62impl Crdt for PNCounter {
63    fn merge(&mut self, other: &Self) {
64        self.increments.merge(&other.increments);
65        self.decrements.merge(&other.decrements);
66    }
67}
68
69/// Delta for [`PNCounter`]: deltas for both the increment and decrement counters.
70#[derive(Debug, Clone, PartialEq, Eq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub struct PNCounterDelta {
73    increments: GCounterDelta,
74    decrements: GCounterDelta,
75}
76
77impl DeltaCrdt for PNCounter {
78    type Delta = PNCounterDelta;
79
80    fn delta(&self, other: &Self) -> PNCounterDelta {
81        PNCounterDelta {
82            increments: self.increments.delta(&other.increments),
83            decrements: self.decrements.delta(&other.decrements),
84        }
85    }
86
87    fn apply_delta(&mut self, delta: &PNCounterDelta) {
88        self.increments.apply_delta(&delta.increments);
89        self.decrements.apply_delta(&delta.decrements);
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn new_counter_is_zero() {
99        let c = PNCounter::new("a");
100        assert_eq!(c.value(), 0);
101    }
102
103    #[test]
104    fn increment_and_decrement() {
105        let mut c = PNCounter::new("a");
106        c.increment();
107        c.increment();
108        c.decrement();
109        assert_eq!(c.value(), 1);
110    }
111
112    #[test]
113    fn can_go_negative() {
114        let mut c = PNCounter::new("a");
115        c.decrement();
116        c.decrement();
117        assert_eq!(c.value(), -2);
118    }
119
120    #[test]
121    fn merge_different_actors() {
122        let mut c1 = PNCounter::new("a");
123        c1.increment();
124        c1.increment();
125
126        let mut c2 = PNCounter::new("b");
127        c2.decrement();
128
129        c1.merge(&c2);
130        assert_eq!(c1.value(), 1); // 2 - 1
131    }
132
133    #[test]
134    fn merge_is_commutative() {
135        let mut c1 = PNCounter::new("a");
136        c1.increment();
137
138        let mut c2 = PNCounter::new("b");
139        c2.decrement();
140        c2.decrement();
141
142        let mut left = c1.clone();
143        left.merge(&c2);
144
145        let mut right = c2.clone();
146        right.merge(&c1);
147
148        assert_eq!(left.value(), right.value());
149    }
150
151    #[test]
152    fn merge_is_idempotent() {
153        let mut c1 = PNCounter::new("a");
154        c1.increment();
155
156        let mut c2 = PNCounter::new("b");
157        c2.decrement();
158
159        c1.merge(&c2);
160        let after_first = c1.clone();
161        c1.merge(&c2);
162
163        assert_eq!(c1, after_first);
164    }
165
166    #[test]
167    fn delta_apply_equivalent_to_merge() {
168        let mut c1 = PNCounter::new("a");
169        c1.increment();
170        c1.increment();
171        c1.decrement();
172
173        let mut c2 = PNCounter::new("b");
174        c2.decrement();
175
176        let mut full = c2.clone();
177        full.merge(&c1);
178
179        let mut via_delta = c2.clone();
180        let d = c1.delta(&c2);
181        via_delta.apply_delta(&d);
182
183        assert_eq!(full.value(), via_delta.value());
184    }
185}