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
101 .map(|ttl| ttl.saturating_sub(self.created_at.elapsed()))
102 }
103}
104
105pub trait CacheBackend: Send + Sync + 'static {
110 fn get<T>(&self, key: &CacheKey) -> impl Future<Output = CacheResult<Option<T>>> + Send
112 where
113 T: serde::de::DeserializeOwned;
114
115 fn set<T>(
117 &self,
118 key: &CacheKey,
119 value: &T,
120 ttl: Option<Duration>,
121 ) -> impl Future<Output = CacheResult<()>> + Send
122 where
123 T: serde::Serialize + Sync;
124
125 fn delete(&self, key: &CacheKey) -> impl Future<Output = CacheResult<bool>> + Send;
127
128 fn exists(&self, key: &CacheKey) -> impl Future<Output = CacheResult<bool>> + Send;
130
131 fn get_many<T>(
135 &self,
136 keys: &[CacheKey],
137 ) -> impl Future<Output = CacheResult<Vec<Option<T>>>> + Send
138 where
139 T: serde::de::DeserializeOwned + Send,
140 {
141 async move {
142 let mut results = Vec::with_capacity(keys.len());
143 for key in keys {
144 results.push(self.get::<T>(key).await?);
145 }
146 Ok(results)
147 }
148 }
149
150 fn set_many<T>(
154 &self,
155 entries: &[(&CacheKey, &T)],
156 ttl: Option<Duration>,
157 ) -> impl Future<Output = CacheResult<()>> + Send
158 where
159 T: serde::Serialize + Sync + Send,
160 {
161 async move {
162 for (key, value) in entries {
163 self.set(*key, *value, ttl).await?;
164 }
165 Ok(())
166 }
167 }
168
169 fn delete_many(&self, keys: &[CacheKey]) -> impl Future<Output = CacheResult<u64>> + Send {
173 async move {
174 let mut count = 0u64;
175 for key in keys {
176 if self.delete(key).await? {
177 count += 1;
178 }
179 }
180 Ok(count)
181 }
182 }
183
184 fn invalidate_pattern(
186 &self,
187 pattern: &KeyPattern,
188 ) -> impl Future<Output = CacheResult<u64>> + Send;
189
190 fn invalidate_tags(&self, tags: &[EntityTag]) -> impl Future<Output = CacheResult<u64>> + Send;
192
193 fn clear(&self) -> impl Future<Output = CacheResult<()>> + Send;
195
196 fn len(&self) -> impl Future<Output = CacheResult<usize>> + Send;
198
199 fn is_empty(&self) -> impl Future<Output = CacheResult<bool>> + Send {
201 async move { Ok(self.len().await? == 0) }
202 }
203
204 fn stats(&self) -> impl Future<Output = CacheResult<BackendStats>> + Send {
206 async move {
207 Ok(BackendStats {
208 entries: self.len().await?,
209 ..Default::default()
210 })
211 }
212 }
213}
214
215#[derive(Debug, Clone, Default)]
217pub struct BackendStats {
218 pub entries: usize,
220 pub memory_bytes: Option<usize>,
222 pub connections: Option<usize>,
224 pub info: Option<String>,
226}
227
228#[derive(Debug, Clone, Copy, Default)]
232pub struct NoopCache;
233
234impl CacheBackend for NoopCache {
235 async fn get<T>(&self, _key: &CacheKey) -> CacheResult<Option<T>>
236 where
237 T: serde::de::DeserializeOwned,
238 {
239 Ok(None)
240 }
241
242 async fn set<T>(&self, _key: &CacheKey, _value: &T, _ttl: Option<Duration>) -> CacheResult<()>
243 where
244 T: serde::Serialize + Sync,
245 {
246 Ok(())
247 }
248
249 async fn delete(&self, _key: &CacheKey) -> CacheResult<bool> {
250 Ok(false)
251 }
252
253 async fn exists(&self, _key: &CacheKey) -> CacheResult<bool> {
254 Ok(false)
255 }
256
257 async fn invalidate_pattern(&self, _pattern: &KeyPattern) -> CacheResult<u64> {
258 Ok(0)
259 }
260
261 async fn invalidate_tags(&self, _tags: &[EntityTag]) -> CacheResult<u64> {
262 Ok(0)
263 }
264
265 async fn clear(&self) -> CacheResult<()> {
266 Ok(())
267 }
268
269 async fn len(&self) -> CacheResult<usize> {
270 Ok(0)
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_cache_entry() {
280 let entry = CacheEntry::new("test value")
281 .with_ttl(Duration::from_secs(60))
282 .with_tags(vec![EntityTag::new("User")]);
283
284 assert!(!entry.is_expired());
285 assert!(entry.remaining_ttl().unwrap() > Duration::from_secs(59));
286 }
287
288 #[tokio::test]
289 async fn test_noop_cache() {
290 let cache = NoopCache;
291
292 cache
294 .set(&CacheKey::new("test", "key"), &"value", None)
295 .await
296 .unwrap();
297
298 let result: Option<String> = cache.get(&CacheKey::new("test", "key")).await.unwrap();
300 assert!(result.is_none());
301 }
302}