mini_moka_wasm/sync/
builder.rs

1use super::Cache;
2use crate::{common::builder_utils, common::concurrent::Weigher, common::time::Duration};
3
4use std::{
5    collections::hash_map::RandomState,
6    hash::{BuildHasher, Hash},
7    marker::PhantomData,
8    sync::Arc,
9};
10
11/// Builds a [`Cache`][cache-struct] or with various configuration knobs.
12///
13/// [cache-struct]: ./struct.Cache.html
14///
15/// # Examples
16///
17/// ```rust
18/// use mini_moka::sync::Cache;
19/// use std::time::Duration;
20///
21/// let cache = Cache::builder()
22///     // Max 10,000 entries
23///     .max_capacity(10_000)
24///     // Time to live (TTL): 30 minutes
25///     .time_to_live(Duration::from_secs(30 * 60))
26///     // Time to idle (TTI):  5 minutes
27///     .time_to_idle(Duration::from_secs( 5 * 60))
28///     // Create the cache.
29///     .build();
30///
31/// // This entry will expire after 5 minutes (TTI) if there is no get().
32/// cache.insert(0, "zero");
33///
34/// // This get() will extend the entry life for another 5 minutes.
35/// cache.get(&0);
36///
37/// // Even though we keep calling get(), the entry will expire
38/// // after 30 minutes (TTL) from the insert().
39/// ```
40///
41#[must_use]
42pub struct CacheBuilder<K, V, C> {
43    max_capacity: Option<u64>,
44    initial_capacity: Option<usize>,
45    weigher: Option<Weigher<K, V>>,
46    time_to_live: Option<Duration>,
47    time_to_idle: Option<Duration>,
48    cache_type: PhantomData<C>,
49}
50
51impl<K, V> Default for CacheBuilder<K, V, Cache<K, V, RandomState>>
52where
53    K: Eq + Hash + Send + Sync + 'static,
54    V: Clone + Send + Sync + 'static,
55{
56    fn default() -> Self {
57        Self {
58            max_capacity: None,
59            initial_capacity: None,
60            weigher: None,
61            time_to_live: None,
62            time_to_idle: None,
63            cache_type: Default::default(),
64        }
65    }
66}
67
68impl<K, V> CacheBuilder<K, V, Cache<K, V, RandomState>>
69where
70    K: Eq + Hash + Send + Sync + 'static,
71    V: Clone + Send + Sync + 'static,
72{
73    /// Construct a new `CacheBuilder` that will be used to build a `Cache` or
74    /// `SegmentedCache` holding up to `max_capacity` entries.
75    pub fn new(max_capacity: u64) -> Self {
76        Self {
77            max_capacity: Some(max_capacity),
78            ..Default::default()
79        }
80    }
81
82    /// Builds a `Cache<K, V>`.
83    ///
84    /// If you want to build a `SegmentedCache<K, V>`, call `segments` method before
85    /// calling this method.
86    ///
87    /// # Panics
88    ///
89    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
90    /// 1000 years. This is done to protect against overflow when computing key
91    /// expiration.
92    pub fn build(self) -> Cache<K, V, RandomState> {
93        let build_hasher = RandomState::default();
94        builder_utils::ensure_expirations_or_panic(self.time_to_live, self.time_to_idle);
95        Cache::with_everything(
96            self.max_capacity,
97            self.initial_capacity,
98            build_hasher,
99            self.weigher,
100            self.time_to_live,
101            self.time_to_idle,
102        )
103    }
104
105    /// Builds a `Cache<K, V, S>`, with the given `hasher`.
106    ///
107    /// If you want to build a `SegmentedCache<K, V>`, call `segments` method  before
108    /// calling this method.
109    ///
110    /// # Panics
111    ///
112    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
113    /// 1000 years. This is done to protect against overflow when computing key
114    /// expiration.
115    pub fn build_with_hasher<S>(self, hasher: S) -> Cache<K, V, S>
116    where
117        S: BuildHasher + Clone + Send + Sync + 'static,
118    {
119        builder_utils::ensure_expirations_or_panic(self.time_to_live, self.time_to_idle);
120        Cache::with_everything(
121            self.max_capacity,
122            self.initial_capacity,
123            hasher,
124            self.weigher,
125            self.time_to_live,
126            self.time_to_idle,
127        )
128    }
129}
130
131impl<K, V, C> CacheBuilder<K, V, C> {
132    /// Sets the max capacity of the cache.
133    pub fn max_capacity(self, max_capacity: u64) -> Self {
134        Self {
135            max_capacity: Some(max_capacity),
136            ..self
137        }
138    }
139
140    /// Sets the initial capacity (number of entries) of the cache.
141    pub fn initial_capacity(self, number_of_entries: usize) -> Self {
142        Self {
143            initial_capacity: Some(number_of_entries),
144            ..self
145        }
146    }
147
148    /// Sets the weigher closure of the cache.
149    ///
150    /// The closure should take `&K` and `&V` as the arguments and returns a `u32`
151    /// representing the relative size of the entry.
152    pub fn weigher(self, weigher: impl Fn(&K, &V) -> u32 + Send + Sync + 'static) -> Self {
153        Self {
154            weigher: Some(Arc::new(weigher)),
155            ..self
156        }
157    }
158
159    /// Sets the time to live of the cache.
160    ///
161    /// A cached entry will be expired after the specified duration past from
162    /// `insert`.
163    ///
164    /// # Panics
165    ///
166    /// `CacheBuilder::build*` methods will panic if the given `duration` is longer
167    /// than 1000 years. This is done to protect against overflow when computing key
168    /// expiration.
169    pub fn time_to_live(self, duration: Duration) -> Self {
170        Self {
171            time_to_live: Some(duration),
172            ..self
173        }
174    }
175
176    /// Sets the time to idle of the cache.
177    ///
178    /// A cached entry will be expired after the specified duration past from `get`
179    /// or `insert`.
180    ///
181    /// # Panics
182    ///
183    /// `CacheBuilder::build*` methods will panic if the given `duration` is longer
184    /// than 1000 years. This is done to protect against overflow when computing key
185    /// expiration.
186    pub fn time_to_idle(self, duration: Duration) -> Self {
187        Self {
188            time_to_idle: Some(duration),
189            ..self
190        }
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::CacheBuilder;
197
198    use std::time::Duration;
199
200    #[test]
201    fn build_cache() {
202        // Cache<char, String>
203        let cache = CacheBuilder::new(100).build();
204        let policy = cache.policy();
205
206        assert_eq!(policy.max_capacity(), Some(100));
207        assert_eq!(policy.time_to_live(), None);
208        assert_eq!(policy.time_to_idle(), None);
209
210        cache.insert('a', "Alice");
211        assert_eq!(cache.get(&'a'), Some("Alice"));
212
213        let cache = CacheBuilder::new(100)
214            .time_to_live(Duration::from_secs(45 * 60))
215            .time_to_idle(Duration::from_secs(15 * 60))
216            .build();
217        let policy = cache.policy();
218
219        assert_eq!(policy.max_capacity(), Some(100));
220        assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60)));
221        assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60)));
222
223        cache.insert('a', "Alice");
224        assert_eq!(cache.get(&'a'), Some("Alice"));
225    }
226
227    #[test]
228    #[should_panic(expected = "time_to_live is longer than 1000 years")]
229    fn build_cache_too_long_ttl() {
230        let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
231        let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
232        let duration = Duration::from_secs(thousand_years_secs);
233        builder
234            .time_to_live(duration + Duration::from_secs(1))
235            .build();
236    }
237
238    #[test]
239    #[should_panic(expected = "time_to_idle is longer than 1000 years")]
240    fn build_cache_too_long_tti() {
241        let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
242        let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
243        let duration = Duration::from_secs(thousand_years_secs);
244        builder
245            .time_to_idle(duration + Duration::from_secs(1))
246            .build();
247    }
248}