tiny_counter/
traits.rs

1use chrono::{DateTime, Utc};
2
3use crate::Result;
4
5/// Clock trait for abstracting time operations.
6///
7/// Provides current time for the event counter system.
8///
9/// This trait enables time-travel testing via TestClock and production
10/// use via SystemClock.
11pub trait Clock: Send + Sync {
12    fn now(&self) -> DateTime<Utc>;
13}
14
15/// Storage backend trait for persisting event data.
16///
17/// Implementations must be thread-safe (Send + Sync) and handle
18/// serialization/deserialization of event data.
19///
20/// The `save` method takes ownership of data to avoid redundant copies.
21/// Most storage backends need to own the data anyway, so this provides
22/// better symmetry with `load()` and eliminates an extra allocation.
23///
24/// ## Transaction Support
25///
26/// Storage backends can optionally implement atomic multi-operation transactions
27/// via `begin_transaction()`, `commit_transaction()`, and `rollback_transaction()`.
28/// Backends that don't support transactions use the default no-op implementations.
29///
30/// When transactions are supported:
31/// - `begin_transaction()` starts a new transaction
32/// - All `save()` and `delete()` calls are buffered until commit
33/// - `commit_transaction()` atomically applies all changes
34/// - `rollback_transaction()` discards pending changes
35///
36/// Backends must be either in transaction mode or normal mode - mixing operations
37/// across modes should return an error.
38pub trait Storage: Send + Sync {
39    fn save(&mut self, key: &str, data: Vec<u8>) -> Result<()>;
40    fn load(&self, key: &str) -> Result<Option<Vec<u8>>>;
41    fn delete(&mut self, key: &str) -> Result<()>;
42    fn list_keys(&self) -> Result<Vec<String>>;
43
44    /// Begins a transaction. All subsequent save/delete operations are buffered
45    /// until commit_transaction() is called.
46    ///
47    /// Default implementation is a no-op for backends that don't support transactions.
48    fn begin_transaction(&mut self) -> Result<()> {
49        Ok(())
50    }
51
52    /// Commits the current transaction, atomically applying all buffered changes.
53    ///
54    /// Default implementation is a no-op for backends that don't support transactions.
55    fn commit_transaction(&mut self) -> Result<()> {
56        Ok(())
57    }
58
59    /// Rolls back the current transaction, discarding all buffered changes.
60    ///
61    /// Default implementation is a no-op for backends that don't support transactions.
62    fn rollback_transaction(&mut self) -> Result<()> {
63        Ok(())
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use std::collections::HashMap;
70
71    use super::*;
72
73    // Test implementation of Clock
74    struct TestClockImpl {
75        time: DateTime<Utc>,
76    }
77
78    impl Clock for TestClockImpl {
79        fn now(&self) -> DateTime<Utc> {
80            self.time
81        }
82    }
83
84    // Test implementation of Storage
85    struct TestStorageImpl {
86        data: HashMap<String, Vec<u8>>,
87    }
88
89    impl Storage for TestStorageImpl {
90        fn save(&mut self, key: &str, data: Vec<u8>) -> Result<()> {
91            self.data.insert(key.to_string(), data);
92            Ok(())
93        }
94
95        fn load(&self, key: &str) -> Result<Option<Vec<u8>>> {
96            Ok(self.data.get(key).cloned())
97        }
98
99        fn delete(&mut self, key: &str) -> Result<()> {
100            self.data.remove(key);
101            Ok(())
102        }
103
104        fn list_keys(&self) -> Result<Vec<String>> {
105            Ok(self.data.keys().cloned().collect())
106        }
107    }
108
109    #[test]
110    fn test_clock_trait_works() {
111        let time = Utc::now();
112        let clock = TestClockImpl { time };
113        assert_eq!(clock.now(), time);
114    }
115
116    #[test]
117    fn test_clock_is_send_sync() {
118        fn assert_send<T: Send>() {}
119        fn assert_sync<T: Sync>() {}
120        assert_send::<Box<dyn Clock>>();
121        assert_sync::<Box<dyn Clock>>();
122    }
123
124    #[test]
125    fn test_storage_save_and_load() {
126        let mut storage = TestStorageImpl {
127            data: HashMap::new(),
128        };
129
130        let data = vec![1, 2, 3, 4];
131        storage.save("test_key", data.clone()).unwrap();
132
133        let loaded = storage.load("test_key").unwrap();
134        assert_eq!(loaded, Some(data));
135    }
136
137    #[test]
138    fn test_storage_load_nonexistent() {
139        let storage = TestStorageImpl {
140            data: HashMap::new(),
141        };
142
143        let loaded = storage.load("nonexistent").unwrap();
144        assert_eq!(loaded, None);
145    }
146
147    #[test]
148    fn test_storage_delete() {
149        let mut storage = TestStorageImpl {
150            data: HashMap::new(),
151        };
152
153        storage.save("test_key", vec![1, 2, 3]).unwrap();
154        storage.delete("test_key").unwrap();
155
156        let loaded = storage.load("test_key").unwrap();
157        assert_eq!(loaded, None);
158    }
159
160    #[test]
161    fn test_storage_is_send_sync() {
162        fn assert_send<T: Send>() {}
163        fn assert_sync<T: Sync>() {}
164        assert_send::<Box<dyn Storage>>();
165        assert_sync::<Box<dyn Storage>>();
166    }
167}