1use dashmap::DashMap;
2use std::hash::Hash;
3use std::sync::Arc;
4
5#[derive(Debug)]
14pub struct Cache<K = String, V = String>
15where
16 K: Clone + Eq + Hash + Send + Sync + 'static,
17 V: Clone + Send + Sync + 'static,
18{
19 buckets: Arc<DashMap<String, Arc<DashMap<K, V>>>>,
20}
21
22impl<K, V> Clone for Cache<K, V>
23where
24 K: Clone + Eq + Hash + Send + Sync + 'static,
25 V: Clone + Send + Sync + 'static,
26{
27 fn clone(&self) -> Self {
28 Self {
29 buckets: Arc::clone(&self.buckets),
30 }
31 }
32}
33
34impl<K, V> Cache<K, V>
35where
36 K: Clone + Eq + Hash + Send + Sync + 'static,
37 V: Clone + Send + Sync + 'static,
38{
39 pub fn new() -> Self {
40 Self {
41 buckets: Arc::new(DashMap::new()),
42 }
43 }
44
45 pub fn with_buckets(names: &[&str]) -> Self {
47 let cache = Self::new();
48 for name in names {
49 cache
50 .buckets
51 .insert(name.to_string(), Arc::new(DashMap::new()));
52 }
53 cache
54 }
55
56 pub fn get(&self, bucket: &str, key: &K) -> Option<V> {
58 self.buckets
59 .get(bucket)
60 .and_then(|b| b.get(key).map(|v| v.clone()))
61 }
62
63 pub fn set(&self, bucket: &str, key: K, value: V) {
65 self.buckets
66 .entry(bucket.to_string())
67 .or_insert_with(|| Arc::new(DashMap::new()))
68 .insert(key, value);
69 }
70
71 pub fn remove(&self, bucket: &str, key: &K) -> Option<V> {
73 self.buckets
74 .get(bucket)
75 .and_then(|b| b.remove(key).map(|(_, v)| v))
76 }
77
78 pub fn clear_bucket(&self, bucket: &str) {
80 if let Some(b) = self.buckets.get(bucket) {
81 b.clear();
82 }
83 }
84
85 pub fn clear_all(&self) {
87 for entry in self.buckets.iter() {
88 entry.value().clear();
89 }
90 }
91}
92
93impl<K, V> Default for Cache<K, V>
94where
95 K: Clone + Eq + Hash + Send + Sync + 'static,
96 V: Clone + Send + Sync + 'static,
97{
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_cache_basic() {
109 let cache: Cache = Cache::with_buckets(&["products", "listings"]);
110
111 cache.set("products", "upc:123".into(), "uuid-1".into());
112 assert_eq!(cache.get("products", &"upc:123".into()), Some("uuid-1".into()));
113 assert_eq!(cache.get("products", &"upc:999".into()), None);
114 assert_eq!(cache.get("listings", &"upc:123".into()), None);
115 }
116
117 #[test]
118 fn test_cache_auto_create_bucket() {
119 let cache: Cache = Cache::new();
120 cache.set("new_bucket", "key".into(), "value".into());
121 assert_eq!(cache.get("new_bucket", &"key".into()), Some("value".into()));
122 }
123
124 #[test]
125 fn test_cache_clear() {
126 let cache: Cache = Cache::with_buckets(&["a", "b"]);
127 cache.set("a", "k1".into(), "v1".into());
128 cache.set("b", "k2".into(), "v2".into());
129
130 cache.clear_bucket("a");
131 assert_eq!(cache.get("a", &"k1".into()), None);
132 assert_eq!(cache.get("b", &"k2".into()), Some("v2".into()));
133
134 cache.clear_all();
135 assert_eq!(cache.get("b", &"k2".into()), None);
136 }
137
138 #[test]
139 fn test_typed_cache_i32_values() {
140 let cache: Cache<String, i32> = Cache::with_buckets(&["counts"]);
141
142 cache.set("counts", "page_views".into(), 42);
143 cache.set("counts", "sessions".into(), 7);
144
145 assert_eq!(cache.get("counts", &"page_views".into()), Some(42));
146 assert_eq!(cache.get("counts", &"sessions".into()), Some(7));
147 assert_eq!(cache.get("counts", &"missing".into()), None);
148
149 cache.remove("counts", &"page_views".into());
150 assert_eq!(cache.get("counts", &"page_views".into()), None);
151 }
152
153 #[test]
154 fn test_typed_cache_i32_keys() {
155 let cache: Cache<i32, String> = Cache::with_buckets(&["users"]);
156
157 cache.set("users", 1, "alice".into());
158 cache.set("users", 2, "bob".into());
159
160 assert_eq!(cache.get("users", &1), Some("alice".into()));
161 assert_eq!(cache.get("users", &2), Some("bob".into()));
162 assert_eq!(cache.get("users", &3), None);
163 }
164
165 #[test]
166 fn test_typed_cache_both_generic() {
167 let cache: Cache<i32, f64> = Cache::new();
168
169 cache.set("scores", 100, 9.5);
170 cache.set("scores", 200, 8.3);
171
172 assert_eq!(cache.get("scores", &100), Some(9.5));
173 assert_eq!(cache.get("scores", &200), Some(8.3));
174 }
175
176 #[test]
177 fn test_typed_cache_vec() {
178 let cache: Cache<String, Vec<String>> = Cache::new();
179
180 cache.set(
181 "tags",
182 "item:1".into(),
183 vec!["rust".into(), "database".into()],
184 );
185
186 let tags = cache.get("tags", &"item:1".into()).unwrap();
187 assert_eq!(tags, vec!["rust".to_string(), "database".to_string()]);
188 }
189
190 #[test]
191 fn test_typed_cache_struct() {
192 #[derive(Debug, Clone, PartialEq)]
193 struct Product {
194 id: i32,
195 name: String,
196 }
197
198 let cache: Cache<String, Product> = Cache::new();
199 cache.set(
200 "products",
201 "sku:abc".into(),
202 Product {
203 id: 1,
204 name: "Widget".into(),
205 },
206 );
207
208 let product = cache.get("products", &"sku:abc".into()).unwrap();
209 assert_eq!(product.id, 1);
210 assert_eq!(product.name, "Widget");
211 }
212}