Skip to main content

salvo_cache/
lib.rs

1#![cfg_attr(test, allow(clippy::unwrap_used))]
2//! Response caching middleware for the Salvo web framework.
3//!
4//! This middleware intercepts HTTP responses and caches them for subsequent
5//! requests, reducing server load and improving response times for cacheable
6//! content.
7//!
8//! # What Gets Cached
9//!
10//! The cache stores the complete response including:
11//! - HTTP status code
12//! - Response headers
13//! - Response body (except for streaming responses)
14//!
15//! # Key Components
16//!
17//! - [`CacheIssuer`]: Determines the cache key for each request
18//! - [`CacheStore`]: Backend storage for cached responses
19//! - [`Cache`]: The middleware handler
20//!
21//! # Default Implementations
22//!
23//! - [`RequestIssuer`]: Generates cache keys from the request URI and method
24//! - [`MokaStore`]: High-performance concurrent cache backed by [`moka`]
25//!
26//! # Example
27//!
28//! ```ignore
29//! use std::time::Duration;
30//! use salvo_cache::{Cache, MokaStore, RequestIssuer};
31//! use salvo_core::prelude::*;
32//!
33//! let cache = Cache::new(
34//!     MokaStore::builder()
35//!         .time_to_live(Duration::from_secs(300))  // Cache for 5 minutes
36//!         .build(),
37//!     RequestIssuer::default(),
38//! );
39//!
40//! let router = Router::new()
41//!     .hoop(cache)
42//!     .get(my_expensive_handler);
43//! ```
44//!
45//! # Custom Cache Keys
46//!
47//! Implement [`CacheIssuer`] to customize cache key generation:
48//!
49//! ```ignore
50//! use salvo_cache::CacheIssuer;
51//!
52//! struct UserBasedIssuer;
53//! impl CacheIssuer for UserBasedIssuer {
54//!     type Key = String;
55//!
56//!     async fn issue(&self, req: &mut Request, depot: &Depot) -> Option<Self::Key> {
57//!         // Cache per user + path
58//!         let user_id = depot.get::<String>("user_id").ok()?;
59//!         Some(format!("{}:{}", user_id, req.uri().path()))
60//!     }
61//! }
62//! ```
63//!
64//! # Skipping Cache
65//!
66//! By default, only GET requests are cached. Use the `skipper` method to customize:
67//!
68//! ```ignore
69//! let cache = Cache::new(store, issuer)
70//!     .skipper(|req, _depot| req.uri().path().starts_with("/api/"));
71//! ```
72//!
73//! # Concurrent Misses
74//!
75//! Concurrent misses for the same cache key are coalesced. One request populates
76//! the cache, and other in-flight requests reuse the generated cache entry when
77//! the response is cacheable.
78//!
79//! # Limitations
80//!
81//! - Streaming responses ([`ResBody::Stream`]) cannot be cached
82//! - Error responses are not cached
83//!
84//! Read more: <https://salvo.rs>
85#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
86#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
87#![cfg_attr(docsrs, feature(doc_cfg))]
88
89use std::borrow::Borrow;
90use std::collections::{HashMap, VecDeque};
91use std::error::Error as StdError;
92use std::fmt::{self, Debug, Formatter};
93use std::hash::Hash;
94use std::sync::{Arc, Mutex, MutexGuard};
95
96use bytes::Bytes;
97use salvo_core::handler::Skipper;
98use salvo_core::http::{HeaderMap, ResBody, StatusCode};
99use salvo_core::{Depot, Error, FlowCtrl, Handler, Request, Response, async_trait};
100use tokio::sync::Notify;
101
102mod skipper;
103pub use skipper::MethodSkipper;
104
105#[macro_use]
106mod cfg;
107
108cfg_feature! {
109    #![feature = "moka-store"]
110
111    pub mod moka_store;
112    pub use moka_store::{MokaStore};
113}
114
115/// Issuer
116pub trait CacheIssuer: Send + Sync + 'static {
117    /// The key is used to identify the rate limit.
118    type Key: Hash + Eq + Send + Sync + 'static;
119    /// Issue a new key for the request. If it returns `None`, the request will not be cached.
120    fn issue(
121        &self,
122        req: &mut Request,
123        depot: &Depot,
124    ) -> impl Future<Output = Option<Self::Key>> + Send;
125}
126impl<F, K> CacheIssuer for F
127where
128    F: Fn(&mut Request, &Depot) -> Option<K> + Send + Sync + 'static,
129    K: Hash + Eq + Send + Sync + 'static,
130{
131    type Key = K;
132    async fn issue(&self, req: &mut Request, depot: &Depot) -> Option<Self::Key> {
133        (self)(req, depot)
134    }
135}
136
137/// Identify user by Request Uri.
138#[derive(Clone, Debug)]
139pub struct RequestIssuer {
140    use_scheme: bool,
141    use_authority: bool,
142    use_path: bool,
143    use_query: bool,
144    use_method: bool,
145}
146impl Default for RequestIssuer {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151impl RequestIssuer {
152    /// Create a new `RequestIssuer`.
153    #[must_use]
154    pub fn new() -> Self {
155        Self {
156            use_scheme: true,
157            use_authority: true,
158            use_path: true,
159            use_query: true,
160            use_method: true,
161        }
162    }
163    /// Whether to use the request's URI scheme when generating the key.
164    #[must_use]
165    pub fn use_scheme(mut self, value: bool) -> Self {
166        self.use_scheme = value;
167        self
168    }
169    /// Whether to use the request's URI authority when generating the key.
170    #[must_use]
171    pub fn use_authority(mut self, value: bool) -> Self {
172        self.use_authority = value;
173        self
174    }
175    /// Whether to use the request's URI path when generating the key.
176    #[must_use]
177    pub fn use_path(mut self, value: bool) -> Self {
178        self.use_path = value;
179        self
180    }
181    /// Whether to use the request's URI query when generating the key.
182    #[must_use]
183    pub fn use_query(mut self, value: bool) -> Self {
184        self.use_query = value;
185        self
186    }
187    /// Whether to use the request method when generating the key.
188    #[must_use]
189    pub fn use_method(mut self, value: bool) -> Self {
190        self.use_method = value;
191        self
192    }
193}
194
195impl CacheIssuer for RequestIssuer {
196    type Key = String;
197    async fn issue(&self, req: &mut Request, _depot: &Depot) -> Option<Self::Key> {
198        let mut key = String::with_capacity(req.uri().path().len() + 16);
199        if self.use_scheme
200            && let Some(scheme) = req.uri().scheme_str()
201        {
202            key.push_str(scheme);
203            key.push_str("://");
204        }
205        if self.use_authority
206            && let Some(authority) = req.uri().authority()
207        {
208            key.push_str(authority.as_str());
209        }
210        if self.use_path {
211            key.push_str(req.uri().path());
212        }
213        if self.use_query
214            && let Some(query) = req.uri().query()
215        {
216            key.push('?');
217            key.push_str(query);
218        }
219        if self.use_method {
220            key.push('|');
221            key.push_str(req.method().as_str());
222        }
223        Some(key)
224    }
225}
226
227/// Store cache.
228pub trait CacheStore: Send + Sync + 'static {
229    /// Error type for CacheStore.
230    type Error: StdError + Sync + Send + 'static;
231    /// Key
232    type Key: Hash + Eq + Send + Clone + 'static;
233    /// Get the cache item from the store.
234    fn load_entry<Q>(&self, key: &Q) -> impl Future<Output = Option<CachedEntry>> + Send
235    where
236        Self::Key: Borrow<Q>,
237        Q: Hash + Eq + Sync;
238    /// Save the cache item to the store.
239    fn save_entry(
240        &self,
241        key: Self::Key,
242        data: CachedEntry,
243    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
244}
245
246fn mutex_lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
247    match mutex.lock() {
248        Ok(guard) => guard,
249        Err(poisoned) => poisoned.into_inner(),
250    }
251}
252
253struct InFlight<K> {
254    entries: Mutex<HashMap<K, Arc<Flight>>>,
255}
256
257impl<K> Default for InFlight<K> {
258    fn default() -> Self {
259        Self {
260            entries: Mutex::new(HashMap::new()),
261        }
262    }
263}
264
265impl<K> InFlight<K>
266where
267    K: Hash + Eq + Clone,
268{
269    fn enter(self: &Arc<Self>, key: K) -> FlightPermit<K> {
270        let mut entries = mutex_lock(&self.entries);
271        if let Some(flight) = entries.get(&key) {
272            return FlightPermit::Follower(flight.clone());
273        }
274
275        let flight = Arc::new(Flight::default());
276        entries.insert(key.clone(), flight.clone());
277        FlightPermit::Leader(FlightGuard {
278            key: Some(key),
279            flight,
280            in_flight: self.clone(),
281        })
282    }
283}
284
285impl<K> InFlight<K>
286where
287    K: Hash + Eq,
288{
289    fn remove(&self, key: &K, flight: &Arc<Flight>) {
290        let mut entries = mutex_lock(&self.entries);
291        if entries
292            .get(key)
293            .is_some_and(|current| Arc::ptr_eq(current, flight))
294        {
295            entries.remove(key);
296        }
297    }
298}
299
300enum FlightPermit<K>
301where
302    K: Hash + Eq,
303{
304    Leader(FlightGuard<K>),
305    Follower(Arc<Flight>),
306}
307
308struct FlightGuard<K>
309where
310    K: Hash + Eq,
311{
312    key: Option<K>,
313    flight: Arc<Flight>,
314    in_flight: Arc<InFlight<K>>,
315}
316
317impl<K> FlightGuard<K>
318where
319    K: Hash + Eq,
320{
321    fn finish(mut self, entry: Option<CachedEntry>) {
322        self.complete(entry);
323    }
324
325    fn complete(&mut self, entry: Option<CachedEntry>) {
326        if let Some(key) = self.key.take() {
327            self.flight.finish(entry);
328            self.in_flight.remove(&key, &self.flight);
329        }
330    }
331}
332
333impl<K> Drop for FlightGuard<K>
334where
335    K: Hash + Eq,
336{
337    fn drop(&mut self) {
338        self.complete(None);
339    }
340}
341
342#[derive(Default)]
343struct Flight {
344    state: Mutex<FlightState>,
345    notify: Notify,
346}
347
348#[derive(Default)]
349struct FlightState {
350    done: bool,
351    entry: Option<CachedEntry>,
352}
353
354impl Flight {
355    async fn wait(&self) {
356        loop {
357            let notified = self.notify.notified();
358            if mutex_lock(&self.state).done {
359                return;
360            }
361            notified.await;
362        }
363    }
364
365    fn finish(&self, entry: Option<CachedEntry>) {
366        *mutex_lock(&self.state) = FlightState { done: true, entry };
367        self.notify.notify_waiters();
368    }
369
370    fn entry(&self) -> Option<CachedEntry> {
371        mutex_lock(&self.state).entry.clone()
372    }
373}
374
375/// `CachedBody` is used to save the response body to `CacheStore`.
376///
377/// [`ResBody`] has a Stream type, which is not `Send + Sync`, so we need to convert it to
378/// `CachedBody`. If the response's body is [`ResBody::Stream`], it will not be cached.
379#[derive(Clone, Debug, PartialEq)]
380#[non_exhaustive]
381pub enum CachedBody {
382    /// No body.
383    None,
384    /// Single bytes body.
385    Once(Bytes),
386    /// Chunks body.
387    Chunks(VecDeque<Bytes>),
388}
389impl TryFrom<&ResBody> for CachedBody {
390    type Error = Error;
391    fn try_from(body: &ResBody) -> Result<Self, Self::Error> {
392        match body {
393            ResBody::None => Ok(Self::None),
394            ResBody::Once(bytes) => Ok(Self::Once(bytes.to_owned())),
395            ResBody::Chunks(chunks) => Ok(Self::Chunks(chunks.to_owned())),
396            _ => Err(Error::other("unsupported body type")),
397        }
398    }
399}
400impl From<CachedBody> for ResBody {
401    fn from(body: CachedBody) -> Self {
402        match body {
403            CachedBody::None => Self::None,
404            CachedBody::Once(bytes) => Self::Once(bytes),
405            CachedBody::Chunks(chunks) => Self::Chunks(chunks),
406        }
407    }
408}
409
410/// Cached entry which will be stored in the cache store.
411#[derive(Clone, Debug)]
412#[non_exhaustive]
413pub struct CachedEntry {
414    /// Response status.
415    pub status: Option<StatusCode>,
416    /// Response headers.
417    pub headers: HeaderMap,
418    /// Response body.
419    ///
420    /// *Notice: If the response's body is streaming, it will be ignored and not cached.
421    pub body: CachedBody,
422}
423impl CachedEntry {
424    /// Create a new `CachedEntry`.
425    pub fn new(status: Option<StatusCode>, headers: HeaderMap, body: CachedBody) -> Self {
426        Self {
427            status,
428            headers,
429            body,
430        }
431    }
432
433    /// Get the response status.
434    pub fn status(&self) -> Option<StatusCode> {
435        self.status
436    }
437
438    /// Get the response headers.
439    pub fn headers(&self) -> &HeaderMap {
440        &self.headers
441    }
442
443    /// Get the response body.
444    ///
445    /// *Notice: If the response's body is streaming, it will be ignored and not cached.
446    pub fn body(&self) -> &CachedBody {
447        &self.body
448    }
449}
450
451/// Cache middleware.
452///
453/// # Example
454///
455/// ```
456/// use std::time::Duration;
457///
458/// use salvo_cache::{Cache, MokaStore, RequestIssuer};
459/// use salvo_core::Router;
460///
461/// let cache = Cache::new(
462///     MokaStore::builder()
463///         .time_to_live(Duration::from_secs(60))
464///         .build(),
465///     RequestIssuer::default(),
466/// );
467/// let router = Router::new().hoop(cache);
468/// ```
469#[non_exhaustive]
470pub struct Cache<S, I>
471where
472    S: CacheStore,
473{
474    /// Cache store.
475    pub store: S,
476    /// Cache issuer.
477    pub issuer: I,
478    /// Skipper.
479    pub skipper: Box<dyn Skipper>,
480    in_flight: Arc<InFlight<S::Key>>,
481}
482impl<S, I> Debug for Cache<S, I>
483where
484    S: CacheStore + Debug,
485    I: Debug,
486{
487    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
488        f.debug_struct("Cache")
489            .field("store", &self.store)
490            .field("issuer", &self.issuer)
491            .finish()
492    }
493}
494
495impl<S, I> Cache<S, I>
496where
497    S: CacheStore,
498{
499    /// Create a new `Cache`.
500    #[inline]
501    #[must_use]
502    pub fn new(store: S, issuer: I) -> Self {
503        let skipper = MethodSkipper::new().skip_all().skip_get(false);
504        Self {
505            store,
506            issuer,
507            skipper: Box::new(skipper),
508            in_flight: Arc::new(InFlight::default()),
509        }
510    }
511    /// Sets skipper and returns a new `Cache`.
512    #[inline]
513    #[must_use]
514    pub fn skipper(mut self, skipper: impl Skipper) -> Self {
515        self.skipper = Box::new(skipper);
516        self
517    }
518}
519
520async fn call_next_and_cache<S>(
521    store: &S,
522    key: S::Key,
523    req: &mut Request,
524    depot: &mut Depot,
525    res: &mut Response,
526    ctrl: &mut FlowCtrl,
527) -> Option<CachedEntry>
528where
529    S: CacheStore,
530{
531    ctrl.call_next(req, depot, res).await;
532    let cached_data = cached_response(res)?;
533    if let Err(e) = store.save_entry(key, cached_data.clone()).await {
534        tracing::error!(error = ?e, "cache failed");
535    }
536    Some(cached_data)
537}
538
539fn cached_response(res: &Response) -> Option<CachedEntry> {
540    if res.body.is_stream() || res.body.is_error() {
541        return None;
542    }
543    let headers = res.headers().clone();
544    let body = match TryInto::<CachedBody>::try_into(&res.body) {
545        Ok(body) => body,
546        Err(e) => {
547            tracing::error!(error = ?e, "cache failed");
548            return None;
549        }
550    };
551    Some(CachedEntry::new(res.status_code, headers, body))
552}
553
554#[async_trait]
555impl<S, I> Handler for Cache<S, I>
556where
557    S: CacheStore<Key = I::Key>,
558    I: CacheIssuer,
559    I::Key: Clone,
560{
561    async fn handle(
562        &self,
563        req: &mut Request,
564        depot: &mut Depot,
565        res: &mut Response,
566        ctrl: &mut FlowCtrl,
567    ) {
568        if self.skipper.skipped(req, depot) {
569            ctrl.call_next(req, depot, res).await;
570            return;
571        }
572        let Some(key) = self.issuer.issue(req, depot).await else {
573            return;
574        };
575        let Some(cache) = self.store.load_entry(&key).await else {
576            match self.in_flight.enter(key.clone()) {
577                FlightPermit::Leader(guard) => {
578                    let cached_data =
579                        call_next_and_cache(&self.store, key, req, depot, res, ctrl).await;
580                    guard.finish(cached_data);
581                }
582                FlightPermit::Follower(flight) => {
583                    flight.wait().await;
584                    if let Some(cache) = flight.entry() {
585                        respond_from_cache(res, cache);
586                        ctrl.skip_rest();
587                    } else {
588                        call_next_and_cache(&self.store, key, req, depot, res, ctrl).await;
589                    }
590                }
591            }
592            return;
593        };
594        respond_from_cache(res, cache);
595        ctrl.skip_rest();
596    }
597}
598
599fn respond_from_cache(res: &mut Response, cache: CachedEntry) {
600    let CachedEntry {
601        status,
602        headers,
603        body,
604    } = cache;
605    if let Some(status) = status {
606        res.status_code(status);
607    }
608    *res.headers_mut() = headers;
609    *res.body_mut() = body.into();
610}
611
612#[cfg(test)]
613mod tests {
614    use std::collections::VecDeque;
615    use std::sync::Arc;
616    use std::sync::atomic::{AtomicUsize, Ordering};
617
618    use bytes::Bytes;
619    use salvo_core::http::HeaderMap;
620    use salvo_core::prelude::*;
621    use salvo_core::test::{ResponseExt, TestClient};
622    use time::OffsetDateTime;
623
624    use super::*;
625
626    #[handler]
627    async fn cached() -> String {
628        format!(
629            "Hello World, my birth time is {}",
630            OffsetDateTime::now_utc()
631        )
632    }
633
634    #[derive(Debug)]
635    struct SlowCached {
636        calls: Arc<AtomicUsize>,
637    }
638
639    #[async_trait]
640    impl Handler for SlowCached {
641        async fn handle(
642            &self,
643            _req: &mut Request,
644            _depot: &mut Depot,
645            res: &mut Response,
646            _ctrl: &mut FlowCtrl,
647        ) {
648            let call = self.calls.fetch_add(1, Ordering::SeqCst) + 1;
649            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
650            res.render(format!("backend call {call}"));
651        }
652    }
653
654    #[tokio::test]
655    async fn test_cache() {
656        let cache = Cache::new(
657            MokaStore::builder()
658                .time_to_live(std::time::Duration::from_secs(5))
659                .build(),
660            RequestIssuer::default(),
661        );
662        let router = Router::new().hoop(cache).goal(cached);
663        let service = Service::new(router);
664
665        let mut res = TestClient::get("http://127.0.0.1:5801")
666            .send(&service)
667            .await;
668        assert_eq!(res.status_code.unwrap(), StatusCode::OK);
669
670        let content0 = res.take_string().await.unwrap();
671
672        let mut res = TestClient::get("http://127.0.0.1:5801")
673            .send(&service)
674            .await;
675        assert_eq!(res.status_code.unwrap(), StatusCode::OK);
676
677        let content1 = res.take_string().await.unwrap();
678        assert_eq!(content0, content1);
679
680        tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
681        let mut res = TestClient::post("http://127.0.0.1:5801")
682            .send(&service)
683            .await;
684        let content2 = res.take_string().await.unwrap();
685
686        assert_ne!(content0, content2);
687    }
688
689    // Tests for RequestIssuer
690    #[test]
691    fn test_request_issuer_new() {
692        let issuer = RequestIssuer::new();
693        assert!(issuer.use_scheme);
694        assert!(issuer.use_authority);
695        assert!(issuer.use_path);
696        assert!(issuer.use_query);
697        assert!(issuer.use_method);
698    }
699
700    #[test]
701    fn test_request_issuer_default() {
702        let issuer = RequestIssuer::default();
703        assert!(issuer.use_scheme);
704        assert!(issuer.use_authority);
705        assert!(issuer.use_path);
706        assert!(issuer.use_query);
707        assert!(issuer.use_method);
708    }
709
710    #[test]
711    fn test_request_issuer_use_scheme() {
712        let issuer = RequestIssuer::new().use_scheme(false);
713        assert!(!issuer.use_scheme);
714        assert!(issuer.use_authority);
715    }
716
717    #[test]
718    fn test_request_issuer_use_authority() {
719        let issuer = RequestIssuer::new().use_authority(false);
720        assert!(issuer.use_scheme);
721        assert!(!issuer.use_authority);
722    }
723
724    #[test]
725    fn test_request_issuer_use_path() {
726        let issuer = RequestIssuer::new().use_path(false);
727        assert!(!issuer.use_path);
728    }
729
730    #[test]
731    fn test_request_issuer_use_query() {
732        let issuer = RequestIssuer::new().use_query(false);
733        assert!(!issuer.use_query);
734    }
735
736    #[test]
737    fn test_request_issuer_use_method() {
738        let issuer = RequestIssuer::new().use_method(false);
739        assert!(!issuer.use_method);
740    }
741
742    #[test]
743    fn test_request_issuer_chain() {
744        let issuer = RequestIssuer::new()
745            .use_scheme(false)
746            .use_authority(false)
747            .use_path(true)
748            .use_query(false)
749            .use_method(true);
750        assert!(!issuer.use_scheme);
751        assert!(!issuer.use_authority);
752        assert!(issuer.use_path);
753        assert!(!issuer.use_query);
754        assert!(issuer.use_method);
755    }
756
757    #[test]
758    fn test_request_issuer_debug() {
759        let issuer = RequestIssuer::new();
760        let debug_str = format!("{issuer:?}");
761        assert!(debug_str.contains("RequestIssuer"));
762        assert!(debug_str.contains("use_scheme"));
763    }
764
765    #[test]
766    fn test_request_issuer_clone() {
767        let issuer = RequestIssuer::new().use_scheme(false);
768        let cloned = issuer.clone();
769        assert_eq!(issuer.use_scheme, cloned.use_scheme);
770        assert_eq!(issuer.use_authority, cloned.use_authority);
771    }
772
773    // Tests for CachedBody
774    #[test]
775    fn test_cached_body_none() {
776        let body = CachedBody::None;
777        assert_eq!(body, CachedBody::None);
778    }
779
780    #[test]
781    fn test_cached_body_once() {
782        let bytes = Bytes::from("test data");
783        let body = CachedBody::Once(bytes.clone());
784        assert_eq!(body, CachedBody::Once(bytes));
785    }
786
787    #[test]
788    fn test_cached_body_chunks() {
789        let mut chunks = VecDeque::new();
790        chunks.push_back(Bytes::from("chunk1"));
791        chunks.push_back(Bytes::from("chunk2"));
792        let body = CachedBody::Chunks(chunks.clone());
793        assert_eq!(body, CachedBody::Chunks(chunks));
794    }
795
796    #[test]
797    fn test_cached_body_try_from_res_body_none() {
798        let res_body = ResBody::None;
799        let result: Result<CachedBody, _> = (&res_body).try_into();
800        assert_eq!(result.unwrap(), CachedBody::None);
801    }
802
803    #[test]
804    fn test_cached_body_try_from_res_body_once() {
805        let bytes = Bytes::from("test");
806        let res_body = ResBody::Once(bytes.clone());
807        let result: Result<CachedBody, _> = (&res_body).try_into();
808        assert_eq!(result.unwrap(), CachedBody::Once(bytes));
809    }
810
811    #[test]
812    fn test_cached_body_try_from_res_body_chunks() {
813        let mut chunks = VecDeque::new();
814        chunks.push_back(Bytes::from("chunk1"));
815        chunks.push_back(Bytes::from("chunk2"));
816        let res_body = ResBody::Chunks(chunks.clone());
817        let result: Result<CachedBody, _> = (&res_body).try_into();
818        assert_eq!(result.unwrap(), CachedBody::Chunks(chunks));
819    }
820
821    #[test]
822    fn test_cached_body_into_res_body_none() {
823        let cb = CachedBody::None;
824        let res_body: ResBody = cb.into();
825        assert!(matches!(res_body, ResBody::None));
826    }
827
828    #[test]
829    fn test_cached_body_into_res_body_once() {
830        let bytes = Bytes::from("test");
831        let cb = CachedBody::Once(bytes.clone());
832        let res_body: ResBody = cb.into();
833        assert!(matches!(res_body, ResBody::Once(b) if b == bytes));
834    }
835
836    #[test]
837    fn test_cached_body_into_res_body_chunks() {
838        let mut chunks = VecDeque::new();
839        chunks.push_back(Bytes::from("chunk1"));
840        let cb = CachedBody::Chunks(chunks);
841        let res_body: ResBody = cb.into();
842        assert!(matches!(res_body, ResBody::Chunks(_)));
843    }
844
845    #[test]
846    fn test_cached_body_debug() {
847        let body = CachedBody::None;
848        let debug_str = format!("{body:?}");
849        assert!(debug_str.contains("None"));
850
851        let body = CachedBody::Once(Bytes::from("test"));
852        let debug_str = format!("{body:?}");
853        assert!(debug_str.contains("Once"));
854    }
855
856    #[test]
857    fn test_cached_body_clone() {
858        let body = CachedBody::Once(Bytes::from("test"));
859        let cloned = body.clone();
860        assert_eq!(body, cloned);
861    }
862
863    // Tests for CachedEntry
864    #[test]
865    fn test_cached_entry_new() {
866        let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), CachedBody::None);
867        assert_eq!(entry.status, Some(StatusCode::OK));
868        assert!(entry.headers.is_empty());
869        assert_eq!(entry.body, CachedBody::None);
870    }
871
872    #[test]
873    fn test_cached_entry_status() {
874        let entry = CachedEntry::new(
875            Some(StatusCode::NOT_FOUND),
876            HeaderMap::new(),
877            CachedBody::None,
878        );
879        assert_eq!(entry.status(), Some(StatusCode::NOT_FOUND));
880    }
881
882    #[test]
883    fn test_cached_entry_status_none() {
884        let entry = CachedEntry::new(None, HeaderMap::new(), CachedBody::None);
885        assert_eq!(entry.status(), None);
886    }
887
888    #[test]
889    fn test_cached_entry_headers() {
890        let mut headers = HeaderMap::new();
891        headers.insert("Content-Type", "application/json".parse().unwrap());
892        let entry = CachedEntry::new(Some(StatusCode::OK), headers.clone(), CachedBody::None);
893        assert_eq!(entry.headers().len(), 1);
894        assert!(entry.headers().contains_key("Content-Type"));
895    }
896
897    #[test]
898    fn test_cached_entry_body() {
899        let body = CachedBody::Once(Bytes::from("test body"));
900        let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), body.clone());
901        assert_eq!(entry.body(), &body);
902    }
903
904    #[test]
905    fn test_cached_entry_debug() {
906        let entry = CachedEntry::new(Some(StatusCode::OK), HeaderMap::new(), CachedBody::None);
907        let debug_str = format!("{entry:?}");
908        assert!(debug_str.contains("CachedEntry"));
909        assert!(debug_str.contains("status"));
910    }
911
912    #[test]
913    fn test_cached_entry_clone() {
914        let entry = CachedEntry::new(
915            Some(StatusCode::OK),
916            HeaderMap::new(),
917            CachedBody::Once(Bytes::from("test")),
918        );
919        let cloned = entry.clone();
920        assert_eq!(entry.status, cloned.status);
921        assert_eq!(entry.body, cloned.body);
922    }
923
924    // Tests for Cache
925    #[test]
926    fn test_cache_new() {
927        let cache = Cache::new(MokaStore::<String>::new(100), RequestIssuer::default());
928        assert!(format!("{cache:?}").contains("Cache"));
929    }
930
931    #[test]
932    fn test_cache_debug() {
933        let cache = Cache::new(MokaStore::<String>::new(100), RequestIssuer::default());
934        let debug_str = format!("{cache:?}");
935        assert!(debug_str.contains("Cache"));
936        assert!(debug_str.contains("store"));
937        assert!(debug_str.contains("issuer"));
938    }
939
940    #[tokio::test]
941    async fn test_cache_same_path_same_content() {
942        let cache = Cache::new(
943            MokaStore::builder()
944                .time_to_live(std::time::Duration::from_secs(60))
945                .build(),
946            RequestIssuer::default(),
947        );
948        let router = Router::new().hoop(cache).goal(cached);
949        let service = Service::new(router);
950
951        let mut res1 = TestClient::get("http://127.0.0.1:5801/same-path")
952            .send(&service)
953            .await;
954        let content1 = res1.take_string().await.unwrap();
955
956        let mut res2 = TestClient::get("http://127.0.0.1:5801/same-path")
957            .send(&service)
958            .await;
959        let content2 = res2.take_string().await.unwrap();
960
961        // Same path should return cached content
962        assert_eq!(content1, content2);
963    }
964
965    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
966    async fn test_cache_coalesces_concurrent_misses() {
967        let calls = Arc::new(AtomicUsize::new(0));
968        let cache = Cache::new(
969            MokaStore::builder()
970                .time_to_live(std::time::Duration::from_secs(60))
971                .build(),
972            RequestIssuer::default(),
973        );
974        let router = Arc::new(Router::new().hoop(cache).goal(SlowCached {
975            calls: calls.clone(),
976        }));
977        let barrier = Arc::new(tokio::sync::Barrier::new(16));
978
979        let mut tasks = Vec::new();
980        for _ in 0..16 {
981            let router = router.clone();
982            let barrier = barrier.clone();
983            tasks.push(tokio::spawn(async move {
984                barrier.wait().await;
985                let mut res = TestClient::get("http://127.0.0.1:5801").send(router).await;
986                res.take_string().await.unwrap()
987            }));
988        }
989
990        let mut bodies = Vec::new();
991        for task in tasks {
992            bodies.push(task.await.unwrap());
993        }
994
995        assert_eq!(calls.load(Ordering::SeqCst), 1);
996        assert!(bodies.iter().all(|body| body == &bodies[0]));
997    }
998}