1use std::collections::HashMap;
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7use tokio::sync::RwLock;
8
9use crate::{CacheAdapter, CacheKey};
10
11#[derive(Debug, Clone)]
13struct CacheEntry {
14 data: Vec<u8>,
16 expires_at: Instant,
18}
19
20impl CacheEntry {
21 fn is_expired(&self) -> bool {
22 Instant::now() >= self.expires_at
23 }
24}
25
26#[derive(Debug, Clone)]
57pub struct InMemoryCache {
58 entries: Arc<RwLock<HashMap<CacheKey, CacheEntry>>>,
60}
61
62impl Default for InMemoryCache {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl InMemoryCache {
69 pub fn new() -> Self {
71 Self {
72 entries: Arc::new(RwLock::new(HashMap::new())),
73 }
74 }
75
76 pub async fn cleanup_expired(&self) {
81 let mut entries = self.entries.write().await;
82 entries.retain(|_, entry| !entry.is_expired());
83 }
84
85 pub async fn len(&self) -> usize {
87 self.entries.read().await.len()
88 }
89
90 pub async fn is_empty(&self) -> bool {
92 self.entries.read().await.is_empty()
93 }
94}
95
96impl CacheAdapter for InMemoryCache {
97 fn get(&self, key: CacheKey) -> crate::CacheGetFuture<'_> {
98 Box::pin(async move {
99 let entries = self.entries.read().await;
100
101 match entries.get(&key) {
102 Some(entry) if !entry.is_expired() => Ok(Some(entry.data.clone())),
103 Some(_) => {
104 drop(entries);
106 let mut entries = self.entries.write().await;
107 entries.remove(&key);
108 Ok(None)
109 }
110 None => Ok(None),
111 }
112 })
113 }
114
115 fn set(&self, key: CacheKey, value: &[u8], ttl: Duration) -> crate::CacheOpFuture<'_> {
116 let value = value.to_vec();
117 Box::pin(async move {
118 let entry = CacheEntry {
119 data: value,
120 expires_at: Instant::now() + ttl,
121 };
122
123 let mut entries = self.entries.write().await;
124 entries.insert(key, entry);
125
126 Ok(())
127 })
128 }
129
130 fn remove(&self, key: CacheKey) -> crate::CacheOpFuture<'_> {
131 Box::pin(async move {
132 let mut entries = self.entries.write().await;
133 entries.remove(&key);
134 Ok(())
135 })
136 }
137
138 fn clear(&self) -> crate::CacheOpFuture<'_> {
139 Box::pin(async move {
140 let mut entries = self.entries.write().await;
141 entries.clear();
142 Ok(())
143 })
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[tokio::test]
152 async fn test_memory_cache_roundtrip() {
153 let cache = InMemoryCache::new();
154 let key = CacheKey::RekorPublicKey;
155 let value = b"test-data";
156
157 assert!(cache.get(key).await.unwrap().is_none());
159
160 cache
162 .set(key, value, Duration::from_secs(3600))
163 .await
164 .unwrap();
165 let retrieved = cache.get(key).await.unwrap().unwrap();
166 assert_eq!(retrieved, value);
167
168 cache.remove(key).await.unwrap();
170 assert!(cache.get(key).await.unwrap().is_none());
171 }
172
173 #[tokio::test]
174 async fn test_memory_cache_expiration() {
175 let cache = InMemoryCache::new();
176 let key = CacheKey::FulcioConfiguration;
177 let value = b"test-config";
178
179 cache
181 .set(key, value, Duration::from_millis(10))
182 .await
183 .unwrap();
184
185 assert!(cache.get(key).await.unwrap().is_some());
187
188 tokio::time::sleep(Duration::from_millis(20)).await;
190
191 assert!(cache.get(key).await.unwrap().is_none());
193 }
194
195 #[tokio::test]
196 async fn test_memory_cache_clear() {
197 let cache = InMemoryCache::new();
198
199 cache
200 .set(CacheKey::RekorPublicKey, b"a", Duration::from_secs(3600))
201 .await
202 .unwrap();
203 cache
204 .set(CacheKey::FulcioTrustBundle, b"b", Duration::from_secs(3600))
205 .await
206 .unwrap();
207
208 assert_eq!(cache.len().await, 2);
209
210 cache.clear().await.unwrap();
211
212 assert!(cache.is_empty().await);
213 }
214
215 #[tokio::test]
216 async fn test_memory_cache_cleanup_expired() {
217 let cache = InMemoryCache::new();
218
219 cache
221 .set(
222 CacheKey::RekorPublicKey,
223 b"long-lived",
224 Duration::from_secs(3600),
225 )
226 .await
227 .unwrap();
228 cache
229 .set(
230 CacheKey::FulcioTrustBundle,
231 b"short-lived",
232 Duration::from_millis(10),
233 )
234 .await
235 .unwrap();
236
237 assert_eq!(cache.len().await, 2);
238
239 tokio::time::sleep(Duration::from_millis(20)).await;
241
242 cache.cleanup_expired().await;
244
245 assert_eq!(cache.len().await, 1);
247 assert!(cache.get(CacheKey::RekorPublicKey).await.unwrap().is_some());
248 assert!(cache
249 .get(CacheKey::FulcioTrustBundle)
250 .await
251 .unwrap()
252 .is_none());
253 }
254}