Skip to main content

rate_net/
builder.rs

1//! The Tier-2 builder.
2
3use std::time::Duration;
4
5use clock_lib::{Clock, SystemClock};
6
7use crate::algorithm::Algorithm;
8use crate::eviction::Eviction;
9use crate::limiter::{RateLimiter, default_shard_count};
10use crate::quota::Quota;
11
12/// A fluent builder for a [`RateLimiter`] when the Tier-1 constructors are not
13/// enough.
14///
15/// Start it with [`RateLimiter::builder`], chain the knobs you care about —
16/// algorithm, quota, burst, shard count, eviction policy, clock — and call
17/// [`build`](Self::build). Anything left unset keeps a sane default: the token
18/// bucket, default sharding, and the bounded-memory [`Eviction`] default. The
19/// quota defaults to a limit of `0` (which denies everything), so set it.
20///
21/// `build` is infallible: a zero limit produces a deny-everything limiter, and a
22/// zero period a degenerate one, rather than an error.
23///
24/// # Examples
25///
26/// ```
27/// use rate_net::{RateLimiter, Eviction};
28/// use std::time::Duration;
29///
30/// let limiter = RateLimiter::builder()
31///     .quota(1000, Duration::from_secs(60)) // 1000 / minute
32///     .burst(50)                            // allow short bursts of 50
33///     .shards(64)                           // tune for core count
34///     .eviction(Eviction::idle(Duration::from_secs(300)))
35///     .build();
36///
37/// assert_eq!(limiter.quota().limit(), 1000);
38/// assert_eq!(limiter.quota().burst(), 50);
39/// assert_eq!(limiter.shards(), 64);
40/// ```
41#[must_use = "a builder does nothing until `.build()` is called"]
42pub struct Builder<C: Clock + Clone = SystemClock> {
43    algorithm: Algorithm,
44    limit: u32,
45    period: Duration,
46    burst: Option<u32>,
47    shards: Option<usize>,
48    eviction: Eviction,
49    clock: C,
50}
51
52impl Builder<SystemClock> {
53    /// Creates a builder with default settings, driven by the OS monotonic
54    /// clock. Reached through [`RateLimiter::builder`].
55    pub(crate) fn new() -> Self {
56        Self {
57            algorithm: Algorithm::default(),
58            limit: 0,
59            period: Duration::from_secs(1),
60            burst: None,
61            shards: None,
62            eviction: Eviction::default(),
63            clock: SystemClock::new(),
64        }
65    }
66}
67
68impl<C: Clock + Clone> Builder<C> {
69    /// Selects the algorithm. Defaults to [`Algorithm::TokenBucket`]. The leaky
70    /// bucket and window algorithms require the `algorithms` feature.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// # #[cfg(feature = "algorithms")] {
76    /// use rate_net::{RateLimiter, Algorithm};
77    /// use std::time::Duration;
78    ///
79    /// let limiter = RateLimiter::builder()
80    ///     .algorithm(Algorithm::SlidingWindowCounter)
81    ///     .quota(100, Duration::from_secs(1))
82    ///     .build();
83    /// assert_eq!(limiter.algorithm(), Algorithm::SlidingWindowCounter);
84    /// # }
85    /// ```
86    pub fn algorithm(mut self, algorithm: Algorithm) -> Self {
87        self.algorithm = algorithm;
88        self
89    }
90
91    /// Sets the quota: `limit` requests per `period`, per key.
92    ///
93    /// # Examples
94    ///
95    /// ```
96    /// use rate_net::RateLimiter;
97    /// use std::time::Duration;
98    ///
99    /// let limiter = RateLimiter::builder().quota(5, Duration::from_millis(100)).build();
100    /// assert_eq!(limiter.quota().limit(), 5);
101    /// ```
102    pub fn quota(mut self, limit: u32, period: Duration) -> Self {
103        self.limit = limit;
104        self.period = period;
105        self
106    }
107
108    /// Sets the quota to `limit` requests per second.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use rate_net::RateLimiter;
114    ///
115    /// let limiter = RateLimiter::builder().per_second(100).build();
116    /// assert_eq!(limiter.quota().limit(), 100);
117    /// ```
118    pub fn per_second(self, limit: u32) -> Self {
119        self.quota(limit, Duration::from_secs(1))
120    }
121
122    /// Sets the quota to `limit` requests per minute.
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// use rate_net::RateLimiter;
128    /// use std::time::Duration;
129    ///
130    /// let limiter = RateLimiter::builder().per_minute(600).build();
131    /// assert_eq!(limiter.quota().period(), Duration::from_secs(60));
132    /// ```
133    pub fn per_minute(self, limit: u32) -> Self {
134        self.quota(limit, Duration::from_secs(60))
135    }
136
137    /// Sets the burst ceiling. Defaults to the quota's limit. Applies to the
138    /// token and leaky buckets; window algorithms ignore it.
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// use rate_net::RateLimiter;
144    ///
145    /// let limiter = RateLimiter::builder().per_second(100).burst(250).build();
146    /// assert_eq!(limiter.quota().burst(), 250);
147    /// ```
148    pub fn burst(mut self, burst: u32) -> Self {
149        self.burst = Some(burst);
150        self
151    }
152
153    /// Sets the shard count (rounded up to a power of two). Defaults to a small
154    /// multiple of the core count.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use rate_net::RateLimiter;
160    ///
161    /// let limiter = RateLimiter::builder().per_second(1).shards(128).build();
162    /// assert_eq!(limiter.shards(), 128);
163    /// ```
164    pub fn shards(mut self, shards: usize) -> Self {
165        self.shards = Some(shards);
166        self
167    }
168
169    /// Sets the eviction policy. Defaults to a bounded-memory capacity cap.
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use rate_net::{RateLimiter, Eviction};
175    ///
176    /// let limiter = RateLimiter::builder().per_second(1).eviction(Eviction::capacity(1_000)).build();
177    /// assert_eq!(limiter.eviction().max_keys(), Some(1_000));
178    /// ```
179    pub fn eviction(mut self, eviction: Eviction) -> Self {
180        self.eviction = eviction;
181        self
182    }
183
184    /// Sets the time source, for deterministic tests with a `ManualClock`.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use rate_net::RateLimiter;
190    /// use clock_lib::ManualClock;
191    /// use std::sync::Arc;
192    ///
193    /// let clock = Arc::new(ManualClock::new());
194    /// let limiter = RateLimiter::builder().per_second(5).clock(Arc::clone(&clock)).build();
195    /// assert!(limiter.check("k").is_allow());
196    /// ```
197    pub fn clock<C2: Clock + Clone>(self, clock: C2) -> Builder<C2> {
198        Builder {
199            algorithm: self.algorithm,
200            limit: self.limit,
201            period: self.period,
202            burst: self.burst,
203            shards: self.shards,
204            eviction: self.eviction,
205            clock,
206        }
207    }
208
209    /// Builds the configured limiter.
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// use rate_net::RateLimiter;
215    /// use std::time::Duration;
216    ///
217    /// let limiter = RateLimiter::builder().quota(10, Duration::from_secs(1)).build();
218    /// assert!(limiter.check("k").is_allow());
219    /// ```
220    #[must_use]
221    pub fn build(self) -> RateLimiter<C> {
222        let burst = self.burst.unwrap_or(self.limit);
223        let quota = Quota::from_parts(self.limit, self.period, burst);
224        let shards = self.shards.unwrap_or_else(default_shard_count);
225        RateLimiter::build(self.algorithm, quota, self.clock, shards, self.eviction)
226    }
227}
228
229#[cfg(all(test, not(loom)))]
230mod tests {
231    #![allow(clippy::unwrap_used)]
232
233    use std::sync::Arc;
234    use std::time::Duration;
235
236    use clock_lib::ManualClock;
237
238    use crate::algorithm::Algorithm;
239    use crate::eviction::Eviction;
240    use crate::limiter::RateLimiter;
241
242    #[test]
243    fn test_builder_defaults_to_token_bucket_and_set_quota() {
244        let limiter = RateLimiter::builder()
245            .quota(50, Duration::from_secs(1))
246            .build();
247        assert_eq!(limiter.algorithm(), Algorithm::TokenBucket);
248        assert_eq!(limiter.quota().limit(), 50);
249        assert_eq!(limiter.quota().burst(), 50);
250    }
251
252    #[test]
253    fn test_builder_covers_every_knob() {
254        let limiter = RateLimiter::builder()
255            .per_minute(1000)
256            .burst(50)
257            .shards(64)
258            .eviction(Eviction::capacity(100_000))
259            .build();
260        assert_eq!(limiter.quota().limit(), 1000);
261        assert_eq!(limiter.quota().period(), Duration::from_secs(60));
262        assert_eq!(limiter.quota().burst(), 50);
263        assert_eq!(limiter.shards(), 64);
264        assert_eq!(limiter.eviction().max_keys(), Some(100_000));
265    }
266
267    #[test]
268    fn test_builder_clock_injection() {
269        let clock = Arc::new(ManualClock::new());
270        let limiter = RateLimiter::builder()
271            .per_second(2)
272            .clock(Arc::clone(&clock))
273            .build();
274        assert!(limiter.check("k").is_allow());
275        assert!(limiter.check("k").is_allow());
276        assert!(limiter.check("k").is_deny());
277        clock.advance(Duration::from_secs(1));
278        assert!(limiter.check("k").is_allow());
279    }
280
281    #[test]
282    fn test_unset_quota_denies() {
283        let limiter = RateLimiter::builder().build();
284        assert!(limiter.check("k").is_deny());
285    }
286
287    #[cfg(feature = "algorithms")]
288    #[test]
289    fn test_builder_selects_algorithm() {
290        let limiter = RateLimiter::builder()
291            .algorithm(Algorithm::FixedWindow)
292            .per_second(10)
293            .build();
294        assert_eq!(limiter.algorithm(), Algorithm::FixedWindow);
295    }
296}