Skip to main content

hitbox_fn/
args.rs

1//! Argument wrapper for function caching.
2
3use hitbox::{
4    CachePolicy, CacheableRequest, Extractor, KeyPart, Predicate, RequestCachePolicy,
5    predicate::PredicateResult,
6};
7
8use crate::KeyExtract;
9
10/// Wrapper for function arguments included in cache key extraction.
11///
12/// This type wraps arguments that should contribute to the cache key.
13/// The inner type must implement `KeyExtract`.
14///
15/// The parameter name is used to disambiguate cache keys:
16/// - Single inner `KeyPart` → key is replaced with the parameter name
17/// - Multiple inner `KeyParts` → each key is prefixed with `"param_name."`
18#[derive(Clone, Debug)]
19pub struct Arg<T> {
20    name: &'static str,
21    value: T,
22}
23
24impl<T> Arg<T> {
25    /// Create a new named argument that will be included in the cache key.
26    pub fn new(name: &'static str, value: T) -> Self {
27        Self { name, value }
28    }
29
30    /// Get a reference to the inner value.
31    pub fn value(&self) -> &T {
32        &self.value
33    }
34
35    /// Unwrap and return the inner value.
36    pub fn into_value(self) -> T {
37        self.value
38    }
39}
40
41impl<T: KeyExtract> KeyExtract for Arg<T> {
42    fn extract(&self) -> Vec<KeyPart> {
43        let inner = self.value.extract();
44        if inner.len() == 1 {
45            inner.into_iter().map(|p| p.with_key(self.name)).collect()
46        } else {
47            inner.into_iter().map(|p| p.prefixed(self.name)).collect()
48        }
49    }
50}
51
52/// Wrapper for function arguments excluded from cache key extraction.
53///
54/// This type wraps arguments that should NOT contribute to the cache key.
55/// The inner type does NOT need to implement `KeyExtract`.
56///
57/// Useful for skipping types like database connections, request contexts,
58/// or other non-cacheable dependencies.
59#[derive(Clone, Debug)]
60pub struct Skipped<T>(T);
61
62impl<T> Skipped<T> {
63    /// Create a new skipped argument that will be excluded from the cache key.
64    pub fn new(value: T) -> Self {
65        Self(value)
66    }
67
68    /// Get a reference to the inner value.
69    pub fn value(&self) -> &T {
70        &self.0
71    }
72
73    /// Unwrap and return the inner value.
74    pub fn into_value(self) -> T {
75        self.0
76    }
77}
78
79impl<T> KeyExtract for Skipped<T> {
80    fn extract(&self) -> Vec<KeyPart> {
81        vec![]
82    }
83}
84
85/// Wrapper around tuple to satisfy orphan rule.
86///
87/// This wrapper enables implementing hitbox traits for tuples of function arguments.
88/// Without this wrapper, we couldn't implement foreign traits (`CacheableRequest`, etc.)
89/// for foreign types (tuples).
90#[derive(Clone, Debug)]
91pub struct Args<T>(pub T);
92
93impl<T> Args<T> {
94    /// Create a new Args wrapper.
95    pub fn new(inner: T) -> Self {
96        Self(inner)
97    }
98
99    /// Unwrap and return the inner value.
100    pub fn into_inner(self) -> T {
101        self.0
102    }
103}
104
105// CacheableRequest implementations for Args<tuples>
106
107macro_rules! impl_cacheable_request_for_args {
108    () => {
109        impl CacheableRequest for Args<()> {
110            async fn cache_policy<P, E>(self, predicates: P, extractors: E) -> RequestCachePolicy<Self>
111            where
112                P: Predicate<Subject = Self> + Send + Sync,
113                E: Extractor<Subject = Self> + Send + Sync,
114            {
115                match predicates.check(self).await {
116                    PredicateResult::Cacheable(subject) => {
117                        let (subject, key) = extractors.get(subject).await.into_cache_key();
118                        CachePolicy::Cacheable(hitbox::CacheablePolicyData::new(key, subject))
119                    }
120                    PredicateResult::NonCacheable(subject) => CachePolicy::NonCacheable(subject),
121                }
122            }
123        }
124    };
125    ($($T:ident),+) => {
126        impl<$($T: Send + Sync + 'static),+> CacheableRequest for Args<($($T,)+)> {
127            async fn cache_policy<P, E>(self, predicates: P, extractors: E) -> RequestCachePolicy<Self>
128            where
129                P: Predicate<Subject = Self> + Send + Sync,
130                E: Extractor<Subject = Self> + Send + Sync,
131            {
132                match predicates.check(self).await {
133                    PredicateResult::Cacheable(subject) => {
134                        let (subject, key) = extractors.get(subject).await.into_cache_key();
135                        CachePolicy::Cacheable(hitbox::CacheablePolicyData::new(key, subject))
136                    }
137                    PredicateResult::NonCacheable(subject) => CachePolicy::NonCacheable(subject),
138                }
139            }
140        }
141    };
142}
143
144impl_cacheable_request_for_args!();
145impl_cacheable_request_for_args!(T0);
146impl_cacheable_request_for_args!(T0, T1);
147impl_cacheable_request_for_args!(T0, T1, T2);
148impl_cacheable_request_for_args!(T0, T1, T2, T3);
149impl_cacheable_request_for_args!(T0, T1, T2, T3, T4);
150impl_cacheable_request_for_args!(T0, T1, T2, T3, T4, T5);
151impl_cacheable_request_for_args!(T0, T1, T2, T3, T4, T5, T6);
152impl_cacheable_request_for_args!(T0, T1, T2, T3, T4, T5, T6, T7);
153impl_cacheable_request_for_args!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
154impl_cacheable_request_for_args!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
155impl_cacheable_request_for_args!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
156impl_cacheable_request_for_args!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);