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