salvo_cache/
moka_store.rs1use std::borrow::Borrow;
3use std::convert::Infallible;
4use std::fmt::{self, Debug, Formatter};
5use std::hash::Hash;
6use std::sync::Arc;
7use std::time::Duration;
8
9use moka::future::Cache as MokaCache;
10use moka::future::CacheBuilder as MokaCacheBuilder;
11use moka::notification::RemovalCause;
12
13use super::{CacheStore, CachedEntry};
14
15pub struct Builder<K> {
17 inner: MokaCacheBuilder<K, CachedEntry, MokaCache<K, CachedEntry>>,
18}
19
20impl<K> Debug for Builder<K> {
21 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
22 f.debug_struct("Builder").finish()
23 }
24}
25impl<K> Builder<K>
26where
27 K: Hash + Eq + Send + Sync + Clone + 'static,
28{
29 #[must_use] pub fn initial_capacity(mut self, capacity: usize) -> Self {
31 self.inner = self.inner.initial_capacity(capacity);
32 self
33 }
34
35 #[must_use] pub fn max_capacity(mut self, capacity: u64) -> Self {
37 self.inner = self.inner.max_capacity(capacity);
38 self
39 }
40
41 #[must_use] pub fn time_to_idle(mut self, duration: Duration) -> Self {
52 self.inner = self.inner.time_to_idle(duration);
53 self
54 }
55
56 #[must_use] pub fn time_to_live(mut self, duration: Duration) -> Self {
67 self.inner = self.inner.time_to_live(duration);
68 self
69 }
70
71 #[must_use]
80 pub fn eviction_listener(
81 mut self,
82 listener: impl Fn(Arc<K>, CachedEntry, RemovalCause) + Send + Sync + 'static,
83 ) -> Self {
84 self.inner = self.inner.eviction_listener(listener);
85 self
86 }
87
88 #[must_use] pub fn build(self) -> MokaStore<K> {
96 MokaStore {
97 inner: self.inner.build(),
98 }
99 }
100}
101pub struct MokaStore<K> {
103 inner: MokaCache<K, CachedEntry>,
104}
105
106impl<K> Debug for MokaStore<K> {
107 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
108 f.debug_struct("MokaStore").finish()
109 }
110}
111impl<K> MokaStore<K>
112where
113 K: Hash + Eq + Send + Sync + Clone + 'static,
114{
115 #[must_use] pub fn new(max_capacity: u64) -> Self {
117 Self {
118 inner: MokaCache::new(max_capacity),
119 }
120 }
121
122 #[must_use] pub fn builder() -> Builder<K> {
124 Builder {
125 inner: MokaCache::builder(),
126 }
127 }
128}
129
130impl<K> CacheStore for MokaStore<K>
131where
132 K: Hash + Eq + Send + Sync + Clone + 'static,
133{
134 type Error = Infallible;
135 type Key = K;
136
137 async fn load_entry<Q>(&self, key: &Q) -> Option<CachedEntry>
138 where
139 Self::Key: Borrow<Q>,
140 Q: Hash + Eq + Sync,
141 {
142 self.inner.get(key).await
143 }
144
145 async fn save_entry(&self, key: Self::Key, entry: CachedEntry) -> Result<(), Self::Error> {
146 self.inner.insert(key, entry).await;
147 Ok(())
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::time::Duration;
155 use salvo_core::http::{HeaderMap, StatusCode};
156 use crate::{CachedBody, CachedEntry};
157
158 #[tokio::test]
159 async fn test_moka_store() {
160 let store = MokaStore::new(100);
161 let key = "test_key".to_string();
162 let entry = CachedEntry {
163 status: Some(StatusCode::OK),
164 headers: HeaderMap::new(),
165 body: CachedBody::Once("test_body".into()),
166 };
167 store.save_entry(key.clone(), entry.clone()).await.unwrap();
168 let loaded_entry = store.load_entry(&key).await.unwrap();
169 assert_eq!(loaded_entry.status, entry.status);
170 assert_eq!(loaded_entry.body, entry.body);
171 }
172
173 #[tokio::test]
174 async fn test_moka_store_builder() {
175 let store = MokaStore::<String>::builder()
176 .initial_capacity(50)
177 .max_capacity(100)
178 .time_to_live(Duration::from_secs(1))
179 .time_to_idle(Duration::from_secs(1))
180 .build();
181 let key = "test_key".to_string();
182 let entry = CachedEntry {
183 status: Some(StatusCode::OK),
184 headers: HeaderMap::new(),
185 body: CachedBody::Once("test_body".into()),
186 };
187 store.save_entry(key.clone(), entry.clone()).await.unwrap();
188 let loaded_entry = store.load_entry(&key).await.unwrap();
189 assert_eq!(loaded_entry.status, entry.status);
190 assert_eq!(loaded_entry.body, entry.body);
191
192 tokio::time::sleep(Duration::from_secs(2)).await;
193 let loaded_entry = store.load_entry(&key).await;
194 assert!(loaded_entry.is_none());
195 }
196
197 #[test]
198 fn test_builder_debug() {
199 let builder = MokaStore::<String>::builder();
200 let dbg_str = format!("{:?}", builder);
201 assert_eq!(dbg_str, "Builder");
202 }
203
204 #[test]
205 fn test_moka_store_debug() {
206 let store = MokaStore::<String>::new(100);
207 let dbg_str = format!("{:?}", store);
208 assert_eq!(dbg_str, "MokaStore");
209 }
210
211 #[tokio::test]
212 async fn test_eviction_listener() {
213 use std::sync::atomic::{AtomicBool, Ordering};
214 let evicted = Arc::new(AtomicBool::new(false));
215 let evicted_clone = evicted.clone();
216 let store = MokaStore::<String>::builder()
217 .max_capacity(1)
218 .eviction_listener(move |_, _, _| {
219 evicted_clone.store(true, Ordering::SeqCst);
220 })
221 .build();
222 let entry = CachedEntry {
223 status: None,
224 headers: HeaderMap::new(),
225 body: CachedBody::Once("test_body".into()),
226 };
227 store.save_entry("key1".to_string(), entry.clone()).await.unwrap();
228 store.save_entry("key2".to_string(), entry.clone()).await.unwrap();
229
230 for _ in 0..10 {
232 store.load_entry(&"key1".to_string()).await;
233 store.load_entry(&"key2".to_string()).await;
234 if evicted.load(Ordering::SeqCst) {
235 break;
236 }
237 tokio::time::sleep(Duration::from_millis(50)).await;
238 }
239
240 assert!(evicted.load(Ordering::SeqCst));
241 }
242}