Skip to main content

hitbox_fn/
cache.rs

1//! Pre-configured cache and typestate markers.
2
3use std::sync::Arc;
4
5use hitbox::backend::CacheBackend;
6use hitbox::concurrency::NoopConcurrencyManager;
7use hitbox::policy::PolicyConfig;
8use hitbox_core::DisabledOffload;
9
10/// Marker: no backend configured.
11pub struct NoBackend;
12
13/// Marker: no policy configured.
14pub struct NoPolicy;
15
16/// Marker: no context requested in response.
17pub struct NoContext;
18
19/// Marker: context requested in response.
20pub struct WithContext;
21
22/// Marker: backend configured.
23pub struct WithBackend<B>(pub Arc<B>);
24
25/// Marker: policy configured.
26pub struct WithPolicy(pub Arc<PolicyConfig>);
27
28/// Pre-configured cache with backend, policy, concurrency manager, and offload manager.
29///
30/// Use [`Cache::builder()`] to create a new cache configuration.
31///
32/// # Example
33///
34/// ```ignore
35/// use hitbox_fn::Cache;
36/// use hitbox::policy::PolicyConfig;
37/// use hitbox_moka::MokaBackend;
38/// use std::time::Duration;
39///
40/// let backend = MokaBackend::builder().max_entries(1000).build();
41/// let policy = PolicyConfig::builder().ttl(Duration::from_secs(60)).build();
42///
43/// let cache = Cache::builder()
44///     .backend(backend)
45///     .policy(policy)
46///     .build();
47///
48/// // Use with cached functions
49/// let result = my_cached_function(arg1, arg2)
50///     .cache(&cache)
51///     .await;
52///
53/// // With offload manager for Stale-While-Revalidate
54/// use hitbox::offload::OffloadManager;
55///
56/// let cache = Cache::builder()
57///     .backend(backend)
58///     .policy(policy)
59///     .offload(OffloadManager::with_defaults())
60///     .build();
61/// ```
62pub struct Cache<B, CM = NoopConcurrencyManager, O = DisabledOffload> {
63    pub(crate) backend: Arc<B>,
64    pub(crate) policy: Arc<PolicyConfig>,
65    pub(crate) concurrency_manager: CM,
66    pub(crate) offload: O,
67}
68
69impl<B, CM, O> Cache<B, CM, O> {
70    /// Get a reference to the backend Arc.
71    pub fn backend(&self) -> &Arc<B> {
72        &self.backend
73    }
74
75    /// Get a reference to the policy Arc.
76    pub fn policy(&self) -> &Arc<PolicyConfig> {
77        &self.policy
78    }
79
80    /// Get a reference to the concurrency manager.
81    pub fn concurrency_manager(&self) -> &CM {
82        &self.concurrency_manager
83    }
84
85    /// Get a reference to the offload manager.
86    pub fn offload(&self) -> &O {
87        &self.offload
88    }
89}
90
91/// Trait for accessing cache internals.
92///
93/// This enables the generated `#[cached]` code to work with different
94/// cache ownership patterns: `&Cache`, `Arc<Cache>`, etc.
95pub trait CacheAccess {
96    /// The cache backend type.
97    type Backend;
98    /// The concurrency manager type.
99    type ConcurrencyManager;
100    /// The offload manager type.
101    type Offload;
102
103    /// Get a shared reference to the backend.
104    fn backend(&self) -> Arc<Self::Backend>;
105    /// Get a shared reference to the policy.
106    fn policy(&self) -> Arc<PolicyConfig>;
107    /// Get the concurrency manager.
108    fn concurrency_manager(&self) -> Self::ConcurrencyManager;
109    /// Get the offload manager.
110    fn offload(&self) -> Self::Offload;
111}
112
113impl<B, CM: Clone, O: Clone> CacheAccess for Cache<B, CM, O> {
114    type Backend = B;
115    type ConcurrencyManager = CM;
116    type Offload = O;
117
118    fn backend(&self) -> Arc<B> {
119        Arc::clone(&self.backend)
120    }
121    fn policy(&self) -> Arc<PolicyConfig> {
122        Arc::clone(&self.policy)
123    }
124    fn concurrency_manager(&self) -> CM {
125        self.concurrency_manager.clone()
126    }
127    fn offload(&self) -> O {
128        self.offload.clone()
129    }
130}
131
132impl<T: CacheAccess> CacheAccess for &T {
133    type Backend = T::Backend;
134    type ConcurrencyManager = T::ConcurrencyManager;
135    type Offload = T::Offload;
136
137    fn backend(&self) -> Arc<Self::Backend> {
138        (**self).backend()
139    }
140    fn policy(&self) -> Arc<PolicyConfig> {
141        (**self).policy()
142    }
143    fn concurrency_manager(&self) -> Self::ConcurrencyManager {
144        (**self).concurrency_manager()
145    }
146    fn offload(&self) -> Self::Offload {
147        (**self).offload()
148    }
149}
150
151impl<T: CacheAccess> CacheAccess for Arc<T> {
152    type Backend = T::Backend;
153    type ConcurrencyManager = T::ConcurrencyManager;
154    type Offload = T::Offload;
155
156    fn backend(&self) -> Arc<Self::Backend> {
157        (**self).backend()
158    }
159    fn policy(&self) -> Arc<PolicyConfig> {
160        (**self).policy()
161    }
162    fn concurrency_manager(&self) -> Self::ConcurrencyManager {
163        (**self).concurrency_manager()
164    }
165    fn offload(&self) -> Self::Offload {
166        (**self).offload()
167    }
168}
169
170impl<B, CM: Clone, O: Clone> Clone for Cache<B, CM, O> {
171    fn clone(&self) -> Self {
172        Self {
173            backend: Arc::clone(&self.backend),
174            policy: Arc::clone(&self.policy),
175            concurrency_manager: self.concurrency_manager.clone(),
176            offload: self.offload.clone(),
177        }
178    }
179}
180
181impl Cache<(), NoopConcurrencyManager, DisabledOffload> {
182    /// Create a new cache builder.
183    pub fn builder() -> CacheBuilder<NoBackend, NoPolicy, NoopConcurrencyManager, DisabledOffload> {
184        CacheBuilder {
185            backend: NoBackend,
186            policy: NoPolicy,
187            concurrency_manager: NoopConcurrencyManager,
188            offload: DisabledOffload,
189        }
190    }
191}
192
193/// Builder for [`Cache`] with typestate pattern.
194///
195/// The builder ensures at compile time that both backend and policy are configured
196/// before the cache can be built.
197pub struct CacheBuilder<B, P, CM, O> {
198    pub(crate) backend: B,
199    pub(crate) policy: P,
200    pub(crate) concurrency_manager: CM,
201    pub(crate) offload: O,
202}
203
204impl<P, CM, O> CacheBuilder<NoBackend, P, CM, O> {
205    /// Set the cache backend.
206    ///
207    /// # Example
208    ///
209    /// ```ignore
210    /// let builder = Cache::builder()
211    ///     .backend(MokaBackend::builder().build());
212    /// ```
213    pub fn backend<B: CacheBackend>(self, backend: B) -> CacheBuilder<WithBackend<B>, P, CM, O> {
214        CacheBuilder {
215            backend: WithBackend(Arc::new(backend)),
216            policy: self.policy,
217            concurrency_manager: self.concurrency_manager,
218            offload: self.offload,
219        }
220    }
221}
222
223impl<B, CM, O> CacheBuilder<B, NoPolicy, CM, O> {
224    /// Set the cache policy configuration.
225    ///
226    /// # Example
227    ///
228    /// ```ignore
229    /// let builder = Cache::builder()
230    ///     .policy(PolicyConfig::builder().ttl(Duration::from_secs(60)).build());
231    /// ```
232    pub fn policy(self, policy: PolicyConfig) -> CacheBuilder<B, WithPolicy, CM, O> {
233        CacheBuilder {
234            backend: self.backend,
235            policy: WithPolicy(Arc::new(policy)),
236            concurrency_manager: self.concurrency_manager,
237            offload: self.offload,
238        }
239    }
240}
241
242impl<B, P, CM, O> CacheBuilder<B, P, CM, O> {
243    /// Set the concurrency manager for dogpile prevention.
244    ///
245    /// By default, [`NoopConcurrencyManager`] is used which doesn't prevent dogpile.
246    /// Use [`BroadcastConcurrencyManager`](hitbox::concurrency::BroadcastConcurrencyManager)
247    /// for production workloads.
248    ///
249    /// # Example
250    ///
251    /// ```ignore
252    /// use hitbox::concurrency::BroadcastConcurrencyManager;
253    ///
254    /// let cache = Cache::builder()
255    ///     .backend(backend)
256    ///     .policy(policy)
257    ///     .concurrency_manager(BroadcastConcurrencyManager::new())
258    ///     .build();
259    /// ```
260    pub fn concurrency_manager<CM2>(self, cm: CM2) -> CacheBuilder<B, P, CM2, O> {
261        CacheBuilder {
262            backend: self.backend,
263            policy: self.policy,
264            concurrency_manager: cm,
265            offload: self.offload,
266        }
267    }
268
269    /// Set the offload manager for background task execution.
270    ///
271    /// By default, [`DisabledOffload`] is used which disables background revalidation.
272    /// Use [`OffloadManager`](hitbox::offload::OffloadManager) to enable
273    /// Stale-While-Revalidate pattern.
274    ///
275    /// # Example
276    ///
277    /// ```ignore
278    /// use hitbox::offload::OffloadManager;
279    ///
280    /// let cache = Cache::builder()
281    ///     .backend(backend)
282    ///     .policy(policy)
283    ///     .offload(OffloadManager::with_defaults())
284    ///     .build();
285    /// ```
286    pub fn offload<O2>(self, offload: O2) -> CacheBuilder<B, P, CM, O2> {
287        CacheBuilder {
288            backend: self.backend,
289            policy: self.policy,
290            concurrency_manager: self.concurrency_manager,
291            offload,
292        }
293    }
294}
295
296impl<B, CM, O> CacheBuilder<WithBackend<B>, WithPolicy, CM, O>
297where
298    B: CacheBackend,
299{
300    /// Build the cache configuration.
301    ///
302    /// This method is only available when both backend and policy are configured.
303    pub fn build(self) -> Cache<B, CM, O> {
304        Cache {
305            backend: self.backend.0,
306            policy: self.policy.0,
307            concurrency_manager: self.concurrency_manager,
308            offload: self.offload,
309        }
310    }
311}