Skip to main content

axum_cache_fred/
strategy.rs

1//!
2//! This module defines the trait and different strategies used to create a cache key for a
3//! response
4//!
5use fred::types::Key;
6use http::Request;
7use tracing::log::*;
8
9use std::time::Duration;
10
11/// Trait used for extensible definition of caching strategies for the middleware
12pub trait CacheStrategy<B> {
13    /// The default time to live for all cached entries
14    ///
15    /// This can be overridden by different strategies but defaults to **1 day**
16    fn ttl(&self) -> Duration {
17        Duration::from_secs(86_400)
18    }
19
20    /// Compute the key for caching.
21    ///
22    /// This function will receive the [Request] sent into the handler so that it may use
23    /// attributes of the incoming request for caching
24    fn computed_key(&self, req: &Request<B>) -> (Duration, Key);
25}
26
27/// A caching strategy which uses the default ttl for all cache keys and uses the (method, path) of
28/// the request route to compute a key.
29///
30/// This caching strategy should only be used in cases where _all_ requests to a given route should
31/// receive the same response(s) and should **not** be used for user-specific or other protected
32/// routes.
33#[derive(Debug, Default, Clone)]
34pub struct RouteKey {}
35
36impl<B> CacheStrategy<B> for RouteKey {
37    fn computed_key(&self, req: &Request<B>) -> (Duration, Key) {
38        let key = format!("{}-{}", req.method(), req.uri().path().to_lowercase());
39        trace!("computed a cache key of `{key}`");
40        (<RouteKey as CacheStrategy<B>>::ttl(self), key.into())
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use pretty_assertions::assert_eq;
48
49    #[test]
50    fn test_route_key() -> anyhow::Result<()> {
51        let strategy = RouteKey::default();
52        let req: Request<()> = Request::builder()
53            .method(http::method::Method::GET)
54            .uri("https://example.com/healthz")
55            .body(())?;
56        let (_, key) = strategy.computed_key(&req);
57        assert_eq!(key, "GET-/healthz".into());
58        Ok(())
59    }
60
61    #[test]
62    fn test_case_insensitive_key() -> anyhow::Result<()> {
63        let strategy = RouteKey::default();
64        let req: Request<()> = Request::builder()
65            .method(http::method::Method::GET)
66            .uri("https://example.com/HealthZ")
67            .body(())?;
68        let (_, key) = strategy.computed_key(&req);
69        assert_eq!(key, "GET-/healthz".into());
70        Ok(())
71    }
72
73    #[test]
74    fn test_key_at_root() -> anyhow::Result<()> {
75        let strategy = RouteKey::default();
76        let req: Request<()> = Request::builder()
77            .method(http::method::Method::GET)
78            .uri("https://example.com/")
79            .body(())?;
80        let (_, key) = strategy.computed_key(&req);
81        assert_eq!(key, "GET-/".into());
82        Ok(())
83    }
84}