1use crate::error::CacheResult;
4use crate::traits::CacheStore;
5use serde::{Serialize, de::DeserializeOwned};
6use std::time::Duration;
7
8pub async fn get<S: CacheStore, T: DeserializeOwned>(
10 store: &S,
11 key: &str,
12) -> CacheResult<Option<T>> {
13 if let Some(json) = store.get_json(key).await? {
14 let value: T = serde_json::from_str(&json)
15 .map_err(|e| crate::error::CacheError::Deserialization(e.to_string()))?;
16 Ok(Some(value))
17 } else {
18 Ok(None)
19 }
20}
21
22pub async fn set<S: CacheStore, T: Serialize>(
28 store: &S,
29 key: &str,
30 value: &T,
31 ttl: Option<Duration>,
32) -> CacheResult<()> {
33 let json = serde_json::to_string(value)
34 .map_err(|e| crate::error::CacheError::Serialization(e.to_string()))?;
35 store.set_json(key, json, ttl).await
36}
37
38pub async fn set_forever<S: CacheStore, T: Serialize>(
43 store: &S,
44 key: &str,
45 value: &T,
46) -> CacheResult<()> {
47 let json = serde_json::to_string(value)
48 .map_err(|e| crate::error::CacheError::Serialization(e.to_string()))?;
49 store.set_json_forever(key, json).await
50}
51
52pub async fn remember<S: CacheStore, T, F, Fut>(
57 store: &S,
58 key: &str,
59 ttl: Duration,
60 factory: F,
61) -> CacheResult<T>
62where
63 T: Serialize + DeserializeOwned,
64 F: FnOnce() -> Fut,
65 Fut: std::future::Future<Output = CacheResult<T>>,
66{
67 if let Some(value) = get(store, key).await? {
68 return Ok(value);
69 }
70
71 let value = factory().await?;
72 set(store, key, &value, Some(ttl)).await?;
73 Ok(value)
74}
75
76pub async fn remember_forever<S: CacheStore, T, F, Fut>(
83 store: &S,
84 key: &str,
85 factory: F,
86) -> CacheResult<T>
87where
88 T: Serialize + DeserializeOwned,
89 F: FnOnce() -> Fut,
90 Fut: std::future::Future<Output = CacheResult<T>>,
91{
92 if let Some(value) = get(store, key).await? {
93 return Ok(value);
94 }
95
96 let value = factory().await?;
97 set_forever(store, key, &value).await?;
98 Ok(value)
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use async_trait::async_trait;
105 use std::collections::HashMap;
106 use std::sync::Arc;
107 use tokio::sync::RwLock;
108
109 type WrittenEntries = Arc<RwLock<HashMap<String, (String, Option<Duration>)>>>;
115
116 struct DefaultTtlCache {
117 default_ttl: Option<Duration>,
118 data: WrittenEntries,
119 }
120
121 impl DefaultTtlCache {
122 fn new(default_ttl: Duration) -> Self {
123 Self {
124 default_ttl: Some(default_ttl),
125 data: Arc::new(RwLock::new(HashMap::new())),
126 }
127 }
128
129 async fn stored_ttl(&self, key: &str) -> Option<Duration> {
130 self.data.read().await.get(key).and_then(|(_, ttl)| *ttl)
131 }
132 }
133
134 #[async_trait]
135 impl CacheStore for DefaultTtlCache {
136 async fn get_json(&self, key: &str) -> CacheResult<Option<String>> {
137 Ok(self.data.read().await.get(key).map(|(v, _)| v.clone()))
138 }
139
140 async fn set_json(
141 &self,
142 key: &str,
143 value: String,
144 ttl: Option<Duration>,
145 ) -> CacheResult<()> {
146 let effective = ttl.or(self.default_ttl);
147 self.data
148 .write()
149 .await
150 .insert(key.to_string(), (value, effective));
151 Ok(())
152 }
153
154 async fn set_json_forever(&self, key: &str, value: String) -> CacheResult<()> {
155 self.data
156 .write()
157 .await
158 .insert(key.to_string(), (value, None));
159 Ok(())
160 }
161
162 async fn delete(&self, key: &str) -> CacheResult<()> {
163 self.data.write().await.remove(key);
164 Ok(())
165 }
166
167 async fn exists(&self, key: &str) -> CacheResult<bool> {
168 Ok(self.data.read().await.contains_key(key))
169 }
170
171 async fn clear(&self) -> CacheResult<()> {
172 self.data.write().await.clear();
173 Ok(())
174 }
175
176 async fn ttl(&self, key: &str) -> CacheResult<Option<Duration>> {
177 Ok(self.stored_ttl(key).await)
178 }
179
180 async fn expire(&self, _key: &str, _ttl: Duration) -> CacheResult<()> {
181 Ok(())
182 }
183
184 async fn increment(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
185 Ok(0)
186 }
187
188 async fn decrement(&self, _key: &str, _delta: i64) -> CacheResult<i64> {
189 Ok(0)
190 }
191 }
192
193 #[tokio::test]
198 async fn test_remember_forever_bypasses_default_ttl() {
199 let store = DefaultTtlCache::new(Duration::from_secs(300));
200
201 let value: i64 = remember_forever(&store, "k", || async { Ok(7) })
202 .await
203 .unwrap();
204 assert_eq!(value, 7);
205
206 assert_eq!(
207 store.stored_ttl("k").await,
208 None,
209 "remember_forever must store without expiry, not with the default TTL"
210 );
211 }
212
213 #[tokio::test]
216 async fn test_set_with_none_ttl_still_uses_default_ttl() {
217 let store = DefaultTtlCache::new(Duration::from_secs(300));
218
219 set(&store, "k", &7_i64, None).await.unwrap();
220 assert_eq!(
221 store.stored_ttl("k").await,
222 Some(Duration::from_secs(300)),
223 "an unspecified TTL must keep falling back to default_ttl"
224 );
225
226 set_forever(&store, "k2", &7_i64).await.unwrap();
227 assert_eq!(store.stored_ttl("k2").await, None);
228 }
229
230 #[tokio::test]
233 async fn test_remember_forever_returns_cached_value() {
234 let store = DefaultTtlCache::new(Duration::from_secs(300));
235 set_forever(&store, "k", &1_i64).await.unwrap();
236
237 let value: i64 = remember_forever(&store, "k", || async {
238 panic!("factory must not run on a cache hit")
239 })
240 .await
241 .unwrap();
242 assert_eq!(value, 1);
243 }
244}