1use crate::{
2 BoxFuture, Container, Injectable, InternalServerErrorException, Module, ModuleMetadata,
3 PayloadTooLargeException,
4};
5use serde::{Serialize, de::DeserializeOwned};
6use serde_json::Value;
7use std::{
8 collections::HashMap,
9 sync::{Arc, RwLock},
10 time::{Duration, Instant},
11};
12
13const DEFAULT_MAX_ENTRIES: usize = 1024;
14const DEFAULT_MAX_VALUE_BYTES: usize = 1024 * 1024;
15
16pub trait CacheStore: Send + Sync + 'static {
17 fn get(&self, key: String) -> BoxFuture<'_, crate::Result<Option<Value>>>;
18 fn set(
19 &self,
20 key: String,
21 value: Value,
22 ttl: Option<Duration>,
23 ) -> BoxFuture<'_, crate::Result<()>>;
24 fn delete(&self, key: String) -> BoxFuture<'_, crate::Result<()>>;
25 fn clear(&self) -> BoxFuture<'_, crate::Result<()>>;
26}
27
28#[derive(Clone)]
29struct CacheEntry {
30 value: Value,
31 inserted_at: Instant,
32 expires_at: Option<Instant>,
33}
34
35impl CacheEntry {
36 fn new(value: Value, ttl: Option<Duration>) -> Self {
37 let now = Instant::now();
38 Self {
39 value,
40 inserted_at: now,
41 expires_at: ttl.and_then(|ttl| now.checked_add(ttl)),
42 }
43 }
44
45 fn is_expired(&self) -> bool {
46 self.expires_at
47 .is_some_and(|expires_at| Instant::now() >= expires_at)
48 }
49}
50
51#[derive(Clone, Debug)]
52pub struct MemoryCacheOptions {
53 pub max_entries: usize,
54 pub max_value_bytes: usize,
55 pub default_ttl: Option<Duration>,
56}
57
58impl Default for MemoryCacheOptions {
59 fn default() -> Self {
60 Self {
61 max_entries: DEFAULT_MAX_ENTRIES,
62 max_value_bytes: DEFAULT_MAX_VALUE_BYTES,
63 default_ttl: None,
64 }
65 }
66}
67
68pub struct MemoryCacheStore {
69 options: MemoryCacheOptions,
70 entries: RwLock<HashMap<String, CacheEntry>>,
71}
72
73impl MemoryCacheStore {
74 pub fn new() -> Self {
75 Self::with_options(MemoryCacheOptions::default())
76 }
77
78 pub fn with_options(options: MemoryCacheOptions) -> Self {
79 Self {
80 options,
81 entries: RwLock::new(HashMap::new()),
82 }
83 }
84
85 pub fn len(&self) -> usize {
86 self.entries.read().map_or(0, |entries| entries.len())
87 }
88
89 pub fn is_empty(&self) -> bool {
90 self.len() == 0
91 }
92
93 fn remove_expired(entries: &mut HashMap<String, CacheEntry>) {
94 entries.retain(|_, entry| !entry.is_expired());
95 }
96
97 fn evict_to_capacity(&self, entries: &mut HashMap<String, CacheEntry>) {
98 if self.options.max_entries == 0 {
99 entries.clear();
100 return;
101 }
102
103 while entries.len() > self.options.max_entries {
104 let Some(oldest_key) = entries
105 .iter()
106 .min_by_key(|(_, entry)| entry.inserted_at)
107 .map(|(key, _)| key.clone())
108 else {
109 return;
110 };
111
112 entries.remove(&oldest_key);
113 }
114 }
115}
116
117impl Default for MemoryCacheStore {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl Injectable for MemoryCacheStore {
124 fn dependencies() -> Vec<crate::ProviderDependency> {
125 crate::provider_dependencies![]
126 }
127
128 fn create(_container: &Container) -> BoxFuture<'_, crate::Result<Self>> {
129 Box::pin(async { Ok(Self::new()) })
130 }
131}
132
133impl CacheStore for MemoryCacheStore {
134 fn get(&self, key: String) -> BoxFuture<'_, crate::Result<Option<Value>>> {
135 Box::pin(async move {
136 let mut entries = self.entries.write().map_err(|_| {
137 InternalServerErrorException::new(anyhow::anyhow!("cache store lock poisoned"))
138 })?;
139 let Some(entry) = entries.get(&key) else {
140 return Ok(None);
141 };
142
143 if entry.is_expired() {
144 entries.remove(&key);
145 return Ok(None);
146 }
147
148 Ok(Some(entry.value.clone()))
149 })
150 }
151
152 fn set(
153 &self,
154 key: String,
155 value: Value,
156 ttl: Option<Duration>,
157 ) -> BoxFuture<'_, crate::Result<()>> {
158 Box::pin(async move {
159 let value_size = serde_json::to_vec(&value)
160 .map_err(InternalServerErrorException::new)?
161 .len();
162
163 if value_size > self.options.max_value_bytes {
164 return Err(PayloadTooLargeException::new(format!(
165 "cache value exceeds the configured limit of {} bytes",
166 self.options.max_value_bytes
167 )));
168 }
169
170 let ttl = ttl.or(self.options.default_ttl);
171 let mut entries = self.entries.write().map_err(|_| {
172 InternalServerErrorException::new(anyhow::anyhow!("cache store lock poisoned"))
173 })?;
174 Self::remove_expired(&mut entries);
175 entries.insert(key, CacheEntry::new(value, ttl));
176 self.evict_to_capacity(&mut entries);
177 Ok(())
178 })
179 }
180
181 fn delete(&self, key: String) -> BoxFuture<'_, crate::Result<()>> {
182 Box::pin(async move {
183 self.entries
184 .write()
185 .map_err(|_| {
186 InternalServerErrorException::new(anyhow::anyhow!("cache store lock poisoned"))
187 })?
188 .remove(&key);
189 Ok(())
190 })
191 }
192
193 fn clear(&self) -> BoxFuture<'_, crate::Result<()>> {
194 Box::pin(async move {
195 self.entries
196 .write()
197 .map_err(|_| {
198 InternalServerErrorException::new(anyhow::anyhow!("cache store lock poisoned"))
199 })?
200 .clear();
201 Ok(())
202 })
203 }
204}
205
206pub struct Cache {
207 store: Arc<dyn CacheStore>,
208}
209
210impl Cache {
211 pub fn new(store: Arc<dyn CacheStore>) -> Self {
212 Self { store }
213 }
214
215 pub async fn get<T>(&self, key: impl Into<String>) -> crate::Result<Option<T>>
216 where
217 T: DeserializeOwned,
218 {
219 let Some(value) = self.store.get(key.into()).await? else {
220 return Ok(None);
221 };
222
223 serde_json::from_value(value)
224 .map(Some)
225 .map_err(InternalServerErrorException::new)
226 }
227
228 pub async fn set<T>(&self, key: impl Into<String>, value: T) -> crate::Result<()>
229 where
230 T: Serialize,
231 {
232 self.set_with_optional_ttl(key, value, None).await
233 }
234
235 pub async fn set_with_ttl<T>(
236 &self,
237 key: impl Into<String>,
238 value: T,
239 ttl: Duration,
240 ) -> crate::Result<()>
241 where
242 T: Serialize,
243 {
244 self.set_with_optional_ttl(key, value, Some(ttl)).await
245 }
246
247 pub async fn set_with_optional_ttl<T>(
248 &self,
249 key: impl Into<String>,
250 value: T,
251 ttl: Option<Duration>,
252 ) -> crate::Result<()>
253 where
254 T: Serialize,
255 {
256 let value = serde_json::to_value(value).map_err(InternalServerErrorException::new)?;
257 self.store.set(key.into(), value, ttl).await
258 }
259
260 pub async fn delete(&self, key: impl Into<String>) -> crate::Result<()> {
261 self.store.delete(key.into()).await
262 }
263
264 pub async fn clear(&self) -> crate::Result<()> {
265 self.store.clear().await
266 }
267}
268
269impl Injectable for Cache {
270 fn dependencies() -> Vec<crate::ProviderDependency> {
271 crate::provider_dependencies![MemoryCacheStore]
272 }
273
274 fn create(container: &Container) -> BoxFuture<'_, crate::Result<Self>> {
275 Box::pin(async move {
276 let store = container.resolve::<MemoryCacheStore>()? as Arc<dyn CacheStore>;
277 Ok(Self::new(store))
278 })
279 }
280}
281
282pub struct CacheModule;
283
284impl Module for CacheModule {
285 fn register() -> ModuleMetadata {
286 ModuleMetadata::new()
287 .provider::<MemoryCacheStore>()
288 .provider::<Cache>()
289 .export::<MemoryCacheStore>()
290 .export::<Cache>()
291 }
292}