rainier_cache/
prefixed.rs1use std::sync::Arc;
4use std::time::Duration;
5
6use rainier_support::{BoxFuture, Result};
7
8use crate::cache::Cache;
9
10pub struct PrefixedCache {
37 inner: Arc<dyn Cache>,
38 prefix: String,
39 with_separator: String,
41}
42
43impl PrefixedCache {
44 pub fn new(inner: Arc<dyn Cache>, prefix: impl Into<String>) -> Self {
46 let prefix = prefix.into();
47 let trimmed = prefix.trim_end_matches(':').to_string();
51 Self { with_separator: format!("{trimmed}:"), prefix: trimmed, inner }
52 }
53
54 pub fn prefix(&self) -> &str {
56 &self.prefix
57 }
58
59 pub fn inner(&self) -> &Arc<dyn Cache> {
61 &self.inner
62 }
63
64 fn key(&self, key: &str) -> String {
65 format!("{}{key}", self.with_separator)
66 }
67}
68
69impl Cache for PrefixedCache {
70 fn name(&self) -> &str {
71 self.inner.name()
72 }
73
74 fn is_shared(&self) -> bool {
75 self.inner.is_shared()
76 }
77
78 fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Vec<u8>>>> {
79 Box::pin(async move {
80 let key = self.key(key);
81 self.inner.get(&key).await
82 })
83 }
84
85 fn put<'a>(
86 &'a self,
87 key: &'a str,
88 value: &'a [u8],
89 ttl: Option<Duration>,
90 ) -> BoxFuture<'a, Result<()>> {
91 Box::pin(async move {
92 let key = self.key(key);
93 self.inner.put(&key, value, ttl).await
94 })
95 }
96
97 fn forget<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>> {
98 Box::pin(async move {
99 let key = self.key(key);
100 self.inner.forget(&key).await
101 })
102 }
103
104 fn flush(&self) -> BoxFuture<'_, Result<()>> {
111 Box::pin(async move {
112 Err(rainier_support::Error::internal(format!(
113 "a prefixed cache cannot flush just `{}:` — no cache backend can delete by \
114 prefix portably. Flush the underlying cache if you really mean everything, \
115 or forget the keys you know about.",
116 self.prefix
117 )))
118 })
119 }
120
121 fn increment<'a>(&'a self, key: &'a str, by: i64) -> BoxFuture<'a, Result<i64>> {
122 Box::pin(async move {
123 let key = self.key(key);
124 self.inner.increment(&key, by).await
125 })
126 }
127
128 fn add<'a>(
133 &'a self,
134 key: &'a str,
135 value: &'a [u8],
136 ttl: Option<Duration>,
137 ) -> BoxFuture<'a, Result<bool>> {
138 Box::pin(async move {
139 let key = self.key(key);
140 self.inner.add(&key, value, ttl).await
141 })
142 }
143
144 fn forget_if<'a>(&'a self, key: &'a str, expected: &'a [u8]) -> BoxFuture<'a, Result<bool>> {
145 Box::pin(async move {
146 let key = self.key(key);
147 self.inner.forget_if(&key, expected).await
148 })
149 }
150
151 fn has<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>> {
152 Box::pin(async move {
153 let key = self.key(key);
154 self.inner.has(&key).await
155 })
156 }
157}
158
159impl std::fmt::Debug for PrefixedCache {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("PrefixedCache")
162 .field("prefix", &self.prefix)
163 .field("inner", &self.inner.name())
164 .finish()
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::cache::CacheExt;
172 use crate::memory::MemoryCache;
173
174 fn shared() -> Arc<MemoryCache> {
175 Arc::new(MemoryCache::new())
176 }
177
178 #[tokio::test]
179 async fn keys_are_namespaced() {
180 let inner = shared();
181 let cache = PrefixedCache::new(Arc::clone(&inner) as Arc<dyn Cache>, "billing");
182
183 cache.put("total", b"10", None).await.unwrap();
184
185 assert_eq!(cache.get("total").await.unwrap(), Some(b"10".to_vec()));
186 assert_eq!(inner.get("billing:total").await.unwrap(), Some(b"10".to_vec()));
187 assert_eq!(inner.get("total").await.unwrap(), None, "the bare key is untouched");
188 }
189
190 #[tokio::test]
191 async fn two_namespaces_do_not_collide() {
192 let inner = shared() as Arc<dyn Cache>;
193 let billing = PrefixedCache::new(Arc::clone(&inner), "billing");
194 let catalogue = PrefixedCache::new(inner, "catalogue");
195
196 billing.put("total", b"10", None).await.unwrap();
197 catalogue.put("total", b"999", None).await.unwrap();
198
199 assert_eq!(billing.get("total").await.unwrap(), Some(b"10".to_vec()));
200 assert_eq!(catalogue.get("total").await.unwrap(), Some(b"999".to_vec()));
201 }
202
203 #[test]
204 fn a_trailing_separator_is_not_a_second_namespace() {
205 let inner = shared() as Arc<dyn Cache>;
206
207 assert_eq!(
208 PrefixedCache::new(Arc::clone(&inner), "app").prefix(),
209 PrefixedCache::new(inner, "app:").prefix()
210 );
211 }
212
213 #[tokio::test]
214 async fn every_operation_is_namespaced() {
215 let inner = shared();
216 let cache = PrefixedCache::new(Arc::clone(&inner) as Arc<dyn Cache>, "app");
217
218 cache.increment("hits", 3).await.unwrap();
219 assert_eq!(inner.get_string("app:hits").await.unwrap().as_deref(), Some("3"));
220
221 assert!(cache.has("hits").await.unwrap());
222 assert!(cache.forget("hits").await.unwrap());
223 assert!(!inner.has("app:hits").await.unwrap());
224 }
225
226 #[tokio::test]
227 async fn flushing_a_namespace_is_refused_rather_than_emptying_the_server() {
228 let inner = shared();
229 let cache = PrefixedCache::new(Arc::clone(&inner) as Arc<dyn Cache>, "app");
230
231 inner.put("someone-elses-key", b"v", None).await.unwrap();
232
233 let err = cache.flush().await.unwrap_err();
234 assert!(err.message().contains("cannot flush"), "{}", err.message());
235 assert!(
236 inner.get("someone-elses-key").await.unwrap().is_some(),
237 "the other application's keys must survive"
238 );
239 }
240
241 #[tokio::test]
242 async fn the_driver_name_passes_through() {
243 let cache = PrefixedCache::new(shared() as Arc<dyn Cache>, "app");
244 assert_eq!(cache.name(), "memory");
245 }
246}