1use crate::events::CacheEvent;
4use crate::eviction::EvictionPolicy;
5use crate::store::CacheStore;
6use crate::{Cache, CacheConfig, KeyExtractor};
7use std::hash::Hash;
8use std::sync::{Arc, Mutex};
9use std::time::Duration;
10use tower::Layer;
11use tower_resilience_core::{EventListeners, FnListener};
12
13#[derive(Clone)]
73pub struct SharedCacheLayer<Req, K, Resp> {
74 config: Arc<CacheConfig<Req, K>>,
75 store: Arc<Mutex<CacheStore<K, Resp>>>,
76}
77
78impl<Req, K, Resp> SharedCacheLayer<Req, K, Resp>
79where
80 K: Hash + Eq + Clone + Send + 'static,
81 Resp: Clone + Send + 'static,
82{
83 pub fn new(config: CacheConfig<Req, K>) -> Self {
85 let store = Arc::new(Mutex::new(CacheStore::new(
86 config.max_size,
87 config.ttl,
88 config.eviction_policy,
89 )));
90 Self {
91 config: Arc::new(config),
92 store,
93 }
94 }
95
96 pub(crate) fn from_config(config: Arc<CacheConfig<Req, K>>) -> Self {
100 let store = Arc::new(Mutex::new(CacheStore::new(
101 config.max_size,
102 config.ttl,
103 config.eviction_policy,
104 )));
105 Self { config, store }
106 }
107
108 pub fn builder() -> SharedCacheConfigBuilder<Req, K, Resp> {
124 SharedCacheConfigBuilder::new()
125 }
126}
127
128impl<S, Req, K, Resp> Layer<S> for SharedCacheLayer<Req, K, Resp>
129where
130 K: Hash + Eq + Clone + Send + 'static,
131 S: tower::Service<Req, Response = Resp>,
132 Resp: Clone + Send + 'static,
133{
134 type Service = Cache<S, Req, K, Resp>;
135
136 fn layer(&self, service: S) -> Self::Service {
137 Cache::with_store(service, Arc::clone(&self.config), Arc::clone(&self.store))
138 }
139}
140
141pub struct SharedCacheConfigBuilder<Req, K, Resp> {
143 max_size: usize,
144 ttl: Option<Duration>,
145 eviction_policy: EvictionPolicy,
146 key_extractor: Option<KeyExtractor<Req, K>>,
147 event_listeners: EventListeners<CacheEvent>,
148 name: String,
149 _resp: std::marker::PhantomData<Resp>,
150}
151
152impl<Req, K, Resp> SharedCacheConfigBuilder<Req, K, Resp>
153where
154 K: Hash + Eq + Clone + Send + 'static,
155 Resp: Clone + Send + 'static,
156{
157 pub fn new() -> Self {
159 Self {
160 max_size: 100,
161 ttl: None,
162 eviction_policy: EvictionPolicy::default(),
163 key_extractor: None,
164 event_listeners: EventListeners::new(),
165 name: String::from("<unnamed>"),
166 _resp: std::marker::PhantomData,
167 }
168 }
169
170 pub fn max_size(mut self, size: usize) -> Self {
174 self.max_size = size;
175 self
176 }
177
178 pub fn ttl(mut self, ttl: Duration) -> Self {
183 self.ttl = Some(ttl);
184 self
185 }
186
187 pub fn eviction_policy(mut self, policy: EvictionPolicy) -> Self {
199 self.eviction_policy = policy;
200 self
201 }
202
203 pub fn key_extractor<F>(mut self, f: F) -> Self
207 where
208 F: Fn(&Req) -> K + Send + Sync + 'static,
209 {
210 self.key_extractor = Some(Arc::new(f));
211 self
212 }
213
214 pub fn name(mut self, name: impl Into<String>) -> Self {
218 self.name = name.into();
219 self
220 }
221
222 pub fn on_hit<F>(mut self, f: F) -> Self
224 where
225 F: Fn() + Send + Sync + 'static,
226 {
227 self.event_listeners.add(FnListener::new(move |event| {
228 if matches!(event, CacheEvent::Hit { .. }) {
229 f();
230 }
231 }));
232 self
233 }
234
235 pub fn on_miss<F>(mut self, f: F) -> Self
237 where
238 F: Fn() + Send + Sync + 'static,
239 {
240 self.event_listeners.add(FnListener::new(move |event| {
241 if matches!(event, CacheEvent::Miss { .. }) {
242 f();
243 }
244 }));
245 self
246 }
247
248 pub fn on_eviction<F>(mut self, f: F) -> Self
250 where
251 F: Fn() + Send + Sync + 'static,
252 {
253 self.event_listeners.add(FnListener::new(move |event| {
254 if matches!(event, CacheEvent::Eviction { .. }) {
255 f();
256 }
257 }));
258 self
259 }
260
261 pub fn build(self) -> Result<SharedCacheLayer<Req, K, Resp>, crate::CacheBuildError> {
268 let key_extractor = self
269 .key_extractor
270 .ok_or(crate::CacheBuildError::MissingKeyExtractor)?;
271
272 let config = CacheConfig {
273 max_size: self.max_size,
274 ttl: self.ttl,
275 eviction_policy: self.eviction_policy,
276 key_extractor,
277 event_listeners: self.event_listeners,
278 name: self.name,
279 };
280
281 Ok(SharedCacheLayer::new(config))
282 }
283}
284
285impl<Req, K, Resp> Default for SharedCacheConfigBuilder<Req, K, Resp>
286where
287 K: Hash + Eq + Clone + Send + 'static,
288 Resp: Clone + Send + 'static,
289{
290 fn default() -> Self {
291 Self::new()
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use std::sync::atomic::{AtomicUsize, Ordering};
299 use tower::service_fn;
300 use tower::{Service, ServiceExt};
301
302 #[derive(Clone, Hash, Eq, PartialEq)]
303 struct TestRequest {
304 id: String,
305 }
306
307 #[test]
308 fn test_shared_builder_defaults() {
309 let _layer: SharedCacheLayer<TestRequest, String, String> = SharedCacheLayer::builder()
310 .key_extractor(|req: &TestRequest| req.id.clone())
311 .build()
312 .unwrap();
313 }
314
315 #[test]
316 fn test_shared_builder_custom_values() {
317 let _layer: SharedCacheLayer<TestRequest, String, String> = SharedCacheLayer::builder()
318 .max_size(500)
319 .ttl(Duration::from_secs(60))
320 .key_extractor(|req: &TestRequest| req.id.clone())
321 .name("my-shared-cache")
322 .build()
323 .unwrap();
324 }
325
326 #[test]
327 fn test_shared_builder_errors_without_key_extractor() {
328 let result = SharedCacheLayer::<TestRequest, String, String>::builder().build();
329 assert!(matches!(
330 result,
331 Err(crate::CacheBuildError::MissingKeyExtractor)
332 ));
333 }
334
335 #[tokio::test]
336 async fn test_shared_cache_across_layer_calls() {
337 let call_count = Arc::new(AtomicUsize::new(0));
338 let cc1 = Arc::clone(&call_count);
339 let cc2 = Arc::clone(&call_count);
340
341 let service1 = service_fn(move |req: String| {
343 let cc = Arc::clone(&cc1);
344 async move {
345 cc.fetch_add(1, Ordering::SeqCst);
346 Ok::<_, std::io::Error>(format!("Response: {}", req))
347 }
348 });
349
350 let service2 = service_fn(move |req: String| {
351 let cc = Arc::clone(&cc2);
352 async move {
353 cc.fetch_add(1, Ordering::SeqCst);
354 Ok::<_, std::io::Error>(format!("Response: {}", req))
355 }
356 });
357
358 let shared_layer: SharedCacheLayer<String, String, String> = SharedCacheLayer::builder()
360 .max_size(10)
361 .key_extractor(|req: &String| req.clone())
362 .build()
363 .unwrap();
364
365 let mut wrapped1 = shared_layer.clone().layer(service1);
367 let mut wrapped2 = shared_layer.layer(service2);
368
369 let response1 = wrapped1
371 .ready()
372 .await
373 .unwrap()
374 .call("test".to_string())
375 .await
376 .unwrap();
377 assert_eq!(response1, "Response: test");
378 assert_eq!(call_count.load(Ordering::SeqCst), 1);
379
380 let response2 = wrapped2
382 .ready()
383 .await
384 .unwrap()
385 .call("test".to_string())
386 .await
387 .unwrap();
388 assert_eq!(response2, "Response: test");
389 assert_eq!(call_count.load(Ordering::SeqCst), 1);
391 }
392
393 #[tokio::test]
394 async fn test_non_shared_cache_layer_creates_separate_stores() {
395 use crate::CacheLayer;
397
398 let call_count = Arc::new(AtomicUsize::new(0));
399 let cc1 = Arc::clone(&call_count);
400 let cc2 = Arc::clone(&call_count);
401
402 let service1 = service_fn(move |req: String| {
403 let cc = Arc::clone(&cc1);
404 async move {
405 cc.fetch_add(1, Ordering::SeqCst);
406 Ok::<_, std::io::Error>(format!("Response: {}", req))
407 }
408 });
409
410 let service2 = service_fn(move |req: String| {
411 let cc = Arc::clone(&cc2);
412 async move {
413 cc.fetch_add(1, Ordering::SeqCst);
414 Ok::<_, std::io::Error>(format!("Response: {}", req))
415 }
416 });
417
418 let layer = CacheLayer::builder()
420 .max_size(10)
421 .key_extractor(|req: &String| req.clone())
422 .build()
423 .unwrap();
424
425 let mut wrapped1 = layer.clone().layer(service1);
427 let mut wrapped2 = layer.layer(service2);
428
429 wrapped1
431 .ready()
432 .await
433 .unwrap()
434 .call("test".to_string())
435 .await
436 .unwrap();
437 assert_eq!(call_count.load(Ordering::SeqCst), 1);
438
439 wrapped2
441 .ready()
442 .await
443 .unwrap()
444 .call("test".to_string())
445 .await
446 .unwrap();
447 assert_eq!(call_count.load(Ordering::SeqCst), 2);
449 }
450}