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 if let Some(b) = self.buckets.get(bucket) {
68 b.insert(key, value);
69 return;
70 }
71 self.buckets
72 .entry(bucket.to_string())
73 .or_insert_with(|| Arc::new(DashMap::new()))
74 .insert(key, value);
75 }
76
77 pub fn remove(&self, bucket: &str, key: &K) -> Option<V> {
79 self.buckets
80 .get(bucket)
81 .and_then(|b| b.remove(key).map(|(_, v)| v))
82 }
83
84 pub fn clear_bucket(&self, bucket: &str) {
86 if let Some(b) = self.buckets.get(bucket) {
87 b.clear();
88 }
89 }
90
91 pub fn clear_all(&self) {
93 for entry in self.buckets.iter() {
94 entry.value().clear();
95 }
96 }
97}
98
99impl<K, V> Default for Cache<K, V>
100where
101 K: Clone + Eq + Hash + Send + Sync + 'static,
102 V: Clone + Send + Sync + 'static,
103{
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn test_cache_basic() {
115 let cache: Cache = Cache::with_buckets(&["products", "listings"]);
116
117 cache.set("products", "upc:123".into(), "uuid-1".into());
118 assert_eq!(cache.get("products", &"upc:123".into()), Some("uuid-1".into()));
119 assert_eq!(cache.get("products", &"upc:999".into()), None);
120 assert_eq!(cache.get("listings", &"upc:123".into()), None);
121 }
122
123 #[test]
124 fn test_cache_auto_create_bucket() {
125 let cache: Cache = Cache::new();
126 cache.set("new_bucket", "key".into(), "value".into());
127 assert_eq!(cache.get("new_bucket", &"key".into()), Some("value".into()));
128 }
129
130 #[test]
131 fn test_cache_clear() {
132 let cache: Cache = Cache::with_buckets(&["a", "b"]);
133 cache.set("a", "k1".into(), "v1".into());
134 cache.set("b", "k2".into(), "v2".into());
135
136 cache.clear_bucket("a");
137 assert_eq!(cache.get("a", &"k1".into()), None);
138 assert_eq!(cache.get("b", &"k2".into()), Some("v2".into()));
139
140 cache.clear_all();
141 assert_eq!(cache.get("b", &"k2".into()), None);
142 }
143
144 #[test]
145 fn test_typed_cache_i32_values() {
146 let cache: Cache<String, i32> = Cache::with_buckets(&["counts"]);
147
148 cache.set("counts", "page_views".into(), 42);
149 cache.set("counts", "sessions".into(), 7);
150
151 assert_eq!(cache.get("counts", &"page_views".into()), Some(42));
152 assert_eq!(cache.get("counts", &"sessions".into()), Some(7));
153 assert_eq!(cache.get("counts", &"missing".into()), None);
154
155 cache.remove("counts", &"page_views".into());
156 assert_eq!(cache.get("counts", &"page_views".into()), None);
157 }
158
159 #[test]
160 fn test_typed_cache_i32_keys() {
161 let cache: Cache<i32, String> = Cache::with_buckets(&["users"]);
162
163 cache.set("users", 1, "alice".into());
164 cache.set("users", 2, "bob".into());
165
166 assert_eq!(cache.get("users", &1), Some("alice".into()));
167 assert_eq!(cache.get("users", &2), Some("bob".into()));
168 assert_eq!(cache.get("users", &3), None);
169 }
170
171 #[test]
172 fn test_typed_cache_both_generic() {
173 let cache: Cache<i32, f64> = Cache::new();
174
175 cache.set("scores", 100, 9.5);
176 cache.set("scores", 200, 8.3);
177
178 assert_eq!(cache.get("scores", &100), Some(9.5));
179 assert_eq!(cache.get("scores", &200), Some(8.3));
180 }
181
182 #[test]
183 fn test_typed_cache_vec() {
184 let cache: Cache<String, Vec<String>> = Cache::new();
185
186 cache.set(
187 "tags",
188 "item:1".into(),
189 vec!["rust".into(), "database".into()],
190 );
191
192 let tags = cache.get("tags", &"item:1".into()).unwrap();
193 assert_eq!(tags, vec!["rust".to_string(), "database".to_string()]);
194 }
195
196 #[test]
197 fn test_typed_cache_struct() {
198 #[derive(Debug, Clone, PartialEq)]
199 struct Product {
200 id: i32,
201 name: String,
202 }
203
204 let cache: Cache<String, Product> = Cache::new();
205 cache.set(
206 "products",
207 "sku:abc".into(),
208 Product {
209 id: 1,
210 name: "Widget".into(),
211 },
212 );
213
214 let product = cache.get("products", &"sku:abc".into()).unwrap();
215 assert_eq!(product.id, 1);
216 assert_eq!(product.name, "Widget");
217 }
218}