use std::time::Duration;
use clock_lib::{Clock, SystemClock};
use crate::algorithm::Algorithm;
use crate::eviction::Eviction;
use crate::limiter::{RateLimiter, default_shard_count};
use crate::quota::Quota;
#[must_use = "a builder does nothing until `.build()` is called"]
pub struct Builder<C: Clock + Clone = SystemClock> {
algorithm: Algorithm,
limit: u32,
period: Duration,
burst: Option<u32>,
shards: Option<usize>,
eviction: Eviction,
clock: C,
}
impl Builder<SystemClock> {
pub(crate) fn new() -> Self {
Self {
algorithm: Algorithm::default(),
limit: 0,
period: Duration::from_secs(1),
burst: None,
shards: None,
eviction: Eviction::default(),
clock: SystemClock::new(),
}
}
}
impl<C: Clock + Clone> Builder<C> {
pub fn algorithm(mut self, algorithm: Algorithm) -> Self {
self.algorithm = algorithm;
self
}
pub fn quota(mut self, limit: u32, period: Duration) -> Self {
self.limit = limit;
self.period = period;
self
}
pub fn per_second(self, limit: u32) -> Self {
self.quota(limit, Duration::from_secs(1))
}
pub fn per_minute(self, limit: u32) -> Self {
self.quota(limit, Duration::from_secs(60))
}
pub fn burst(mut self, burst: u32) -> Self {
self.burst = Some(burst);
self
}
pub fn shards(mut self, shards: usize) -> Self {
self.shards = Some(shards);
self
}
pub fn eviction(mut self, eviction: Eviction) -> Self {
self.eviction = eviction;
self
}
pub fn clock<C2: Clock + Clone>(self, clock: C2) -> Builder<C2> {
Builder {
algorithm: self.algorithm,
limit: self.limit,
period: self.period,
burst: self.burst,
shards: self.shards,
eviction: self.eviction,
clock,
}
}
#[must_use]
pub fn build(self) -> RateLimiter<C> {
let burst = self.burst.unwrap_or(self.limit);
let quota = Quota::from_parts(self.limit, self.period, burst);
let shards = self.shards.unwrap_or_else(default_shard_count);
RateLimiter::build(self.algorithm, quota, self.clock, shards, self.eviction)
}
}
#[cfg(all(test, not(loom)))]
mod tests {
#![allow(clippy::unwrap_used)]
use std::sync::Arc;
use std::time::Duration;
use clock_lib::ManualClock;
use crate::algorithm::Algorithm;
use crate::eviction::Eviction;
use crate::limiter::RateLimiter;
#[test]
fn test_builder_defaults_to_token_bucket_and_set_quota() {
let limiter = RateLimiter::builder()
.quota(50, Duration::from_secs(1))
.build();
assert_eq!(limiter.algorithm(), Algorithm::TokenBucket);
assert_eq!(limiter.quota().limit(), 50);
assert_eq!(limiter.quota().burst(), 50);
}
#[test]
fn test_builder_covers_every_knob() {
let limiter = RateLimiter::builder()
.per_minute(1000)
.burst(50)
.shards(64)
.eviction(Eviction::capacity(100_000))
.build();
assert_eq!(limiter.quota().limit(), 1000);
assert_eq!(limiter.quota().period(), Duration::from_secs(60));
assert_eq!(limiter.quota().burst(), 50);
assert_eq!(limiter.shards(), 64);
assert_eq!(limiter.eviction().max_keys(), Some(100_000));
}
#[test]
fn test_builder_clock_injection() {
let clock = Arc::new(ManualClock::new());
let limiter = RateLimiter::builder()
.per_second(2)
.clock(Arc::clone(&clock))
.build();
assert!(limiter.check("k").is_allow());
assert!(limiter.check("k").is_allow());
assert!(limiter.check("k").is_deny());
clock.advance(Duration::from_secs(1));
assert!(limiter.check("k").is_allow());
}
#[test]
fn test_unset_quota_denies() {
let limiter = RateLimiter::builder().build();
assert!(limiter.check("k").is_deny());
}
#[cfg(feature = "algorithms")]
#[test]
fn test_builder_selects_algorithm() {
let limiter = RateLimiter::builder()
.algorithm(Algorithm::FixedWindow)
.per_second(10)
.build();
assert_eq!(limiter.algorithm(), Algorithm::FixedWindow);
}
}