prax_query/data_cache/
backend.rs1use super::invalidation::EntityTag;
4use super::key::{CacheKey, KeyPattern};
5use std::future::Future;
6use std::time::Duration;
7use thiserror::Error;
8
9#[derive(Error, Debug)]
11pub enum CacheError {
12 #[error("serialization error: {0}")]
14 Serialization(String),
15
16 #[error("deserialization error: {0}")]
18 Deserialization(String),
19
20 #[error("connection error: {0}")]
22 Connection(String),
23
24 #[error("operation timed out")]
26 Timeout,
27
28 #[error("key not found: {0}")]
30 NotFound(String),
31
32 #[error("backend error: {0}")]
34 Backend(String),
35
36 #[error("configuration error: {0}")]
38 Config(String),
39}
40
41pub type CacheResult<T> = Result<T, CacheError>;
43
44#[derive(Debug, Clone)]
46pub struct CacheEntry<T> {
47 pub value: T,
49 pub created_at: std::time::Instant,
51 pub ttl: Option<Duration>,
53 pub tags: Vec<EntityTag>,
55 pub size_bytes: Option<usize>,
57}
58
59impl<T> CacheEntry<T> {
60 pub fn new(value: T) -> Self {
62 Self {
63 value,
64 created_at: std::time::Instant::now(),
65 ttl: None,
66 tags: Vec::new(),
67 size_bytes: None,
68 }
69 }
70
71 pub fn with_ttl(mut self, ttl: Duration) -> Self {
73 self.ttl = Some(ttl);
74 self
75 }
76
77 pub fn with_tags(mut self, tags: Vec<EntityTag>) -> Self {
79 self.tags = tags;
80 self
81 }
82
83 pub fn with_size(mut self, size: usize) -> Self {
85 self.size_bytes = Some(size);
86 self
87 }
88
89 pub fn is_expired(&self) -> bool {
91 if let Some(ttl) = self.ttl {
92 self.created_at.elapsed() >= ttl
93 } else {
94 false
95 }
96 }
97
98 pub fn remaining_ttl(&self) -> Option<Duration> {
100 self.ttl.map(|ttl| ttl.saturating_sub(self.created_at.elapsed()))
101 }
102}
103
104pub trait CacheBackend: Send + Sync + 'static {
109 fn get<T>(&self, key: &CacheKey) -> impl Future<Output = CacheResult<Option<T>>> + Send
111 where
112 T: serde::de::DeserializeOwned;
113
114 fn set<T>(
116 &self,
117 key: &CacheKey,
118 value: &T,
119 ttl: Option<Duration>,
120 ) -> impl Future<Output = CacheResult<()>> + Send
121 where
122 T: serde::Serialize + Sync;
123
124 fn delete(&self, key: &CacheKey) -> impl Future<Output = CacheResult<bool>> + Send;
126
127 fn exists(&self, key: &CacheKey) -> impl Future<Output = CacheResult<bool>> + Send;
129
130 fn get_many<T>(
134 &self,
135 keys: &[CacheKey],
136 ) -> impl Future<Output = CacheResult<Vec<Option<T>>>> + Send
137 where
138 T: serde::de::DeserializeOwned + Send,
139 {
140 async move {
141 let mut results = Vec::with_capacity(keys.len());
142 for key in keys {
143 results.push(self.get::<T>(key).await?);
144 }
145 Ok(results)
146 }
147 }
148
149 fn set_many<T>(
153 &self,
154 entries: &[(&CacheKey, &T)],
155 ttl: Option<Duration>,
156 ) -> impl Future<Output = CacheResult<()>> + Send
157 where
158 T: serde::Serialize + Sync + Send,
159 {
160 async move {
161 for (key, value) in entries {
162 self.set(*key, *value, ttl).await?;
163 }
164 Ok(())
165 }
166 }
167
168 fn delete_many(&self, keys: &[CacheKey]) -> impl Future<Output = CacheResult<u64>> + Send {
172 async move {
173 let mut count = 0u64;
174 for key in keys {
175 if self.delete(key).await? {
176 count += 1;
177 }
178 }
179 Ok(count)
180 }
181 }
182
183 fn invalidate_pattern(
185 &self,
186 pattern: &KeyPattern,
187 ) -> impl Future<Output = CacheResult<u64>> + Send;
188
189 fn invalidate_tags(&self, tags: &[EntityTag]) -> impl Future<Output = CacheResult<u64>> + Send;
191
192 fn clear(&self) -> impl Future<Output = CacheResult<()>> + Send;
194
195 fn len(&self) -> impl Future<Output = CacheResult<usize>> + Send;
197
198 fn is_empty(&self) -> impl Future<Output = CacheResult<bool>> + Send {
200 async move { Ok(self.len().await? == 0) }
201 }
202
203 fn stats(&self) -> impl Future<Output = CacheResult<BackendStats>> + Send {
205 async move {
206 Ok(BackendStats {
207 entries: self.len().await?,
208 ..Default::default()
209 })
210 }
211 }
212}
213
214#[derive(Debug, Clone, Default)]
216pub struct BackendStats {
217 pub entries: usize,
219 pub memory_bytes: Option<usize>,
221 pub connections: Option<usize>,
223 pub info: Option<String>,
225}
226
227#[derive(Debug, Clone, Copy, Default)]
231pub struct NoopCache;
232
233impl CacheBackend for NoopCache {
234 async fn get<T>(&self, _key: &CacheKey) -> CacheResult<Option<T>>
235 where
236 T: serde::de::DeserializeOwned,
237 {
238 Ok(None)
239 }
240
241 async fn set<T>(
242 &self,
243 _key: &CacheKey,
244 _value: &T,
245 _ttl: Option<Duration>,
246 ) -> CacheResult<()>
247 where
248 T: serde::Serialize + Sync,
249 {
250 Ok(())
251 }
252
253 async fn delete(&self, _key: &CacheKey) -> CacheResult<bool> {
254 Ok(false)
255 }
256
257 async fn exists(&self, _key: &CacheKey) -> CacheResult<bool> {
258 Ok(false)
259 }
260
261 async fn invalidate_pattern(&self, _pattern: &KeyPattern) -> CacheResult<u64> {
262 Ok(0)
263 }
264
265 async fn invalidate_tags(&self, _tags: &[EntityTag]) -> CacheResult<u64> {
266 Ok(0)
267 }
268
269 async fn clear(&self) -> CacheResult<()> {
270 Ok(())
271 }
272
273 async fn len(&self) -> CacheResult<usize> {
274 Ok(0)
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn test_cache_entry() {
284 let entry = CacheEntry::new("test value")
285 .with_ttl(Duration::from_secs(60))
286 .with_tags(vec![EntityTag::new("User")]);
287
288 assert!(!entry.is_expired());
289 assert!(entry.remaining_ttl().unwrap() > Duration::from_secs(59));
290 }
291
292 #[tokio::test]
293 async fn test_noop_cache() {
294 let cache = NoopCache;
295
296 cache
298 .set(&CacheKey::new("test", "key"), &"value", None)
299 .await
300 .unwrap();
301
302 let result: Option<String> = cache.get(&CacheKey::new("test", "key")).await.unwrap();
304 assert!(result.is_none());
305 }
306}
307
308