use super::FixedWindowRateLimiter;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct FixedWindow {
pub(super) name: Option<String>,
max_requests: u32,
window_size: Duration,
eviction: Option<Duration>,
}
impl FixedWindow {
#[inline]
pub fn new(max_requests: u32, window_size: Duration) -> Self {
Self {
name: None,
eviction: None,
max_requests,
window_size,
}
}
#[inline]
pub fn with_eviction(mut self, eviction: Duration) -> Self {
self.eviction = Some(eviction);
self
}
#[inline]
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[inline]
pub(super) fn build(&self) -> FixedWindowRateLimiter {
let mut limiter = FixedWindowRateLimiter::new(self.max_requests, self.window_size);
if let Some(eviction) = self.eviction {
limiter.set_eviction(eviction);
}
limiter
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn it_creates_new_fixed_window_with_basic_parameters() {
let policy = FixedWindow::new(100, Duration::from_secs(60));
assert_eq!(policy.max_requests, 100);
assert_eq!(policy.window_size, Duration::from_secs(60));
assert!(policy.name.is_none());
assert!(policy.eviction.is_none());
}
#[test]
fn it_sets_eviction_period() {
let policy =
FixedWindow::new(100, Duration::from_secs(60)).with_eviction(Duration::from_secs(300));
assert_eq!(policy.eviction, Some(Duration::from_secs(300)));
}
#[test]
fn it_sets_policy_name_from_string() {
let policy = FixedWindow::new(100, Duration::from_secs(60)).with_name("api_limiter");
assert_eq!(policy.name, Some("api_limiter".to_string()));
}
#[test]
fn it_sets_policy_name_from_string_slice() {
let name = String::from("test_policy");
let policy = FixedWindow::new(100, Duration::from_secs(60)).with_name(name.clone());
assert_eq!(policy.name, Some(name));
}
#[test]
fn it_chains_multiple_builder_methods() {
let policy = FixedWindow::new(50, Duration::from_secs(30))
.with_name("chained_policy")
.with_eviction(Duration::from_secs(600));
assert_eq!(policy.max_requests, 50);
assert_eq!(policy.window_size, Duration::from_secs(30));
assert_eq!(policy.name, Some("chained_policy".to_string()));
assert_eq!(policy.eviction, Some(Duration::from_secs(600)));
}
#[test]
fn it_builds_rate_limiter_without_eviction() {
let policy = FixedWindow::new(100, Duration::from_secs(60));
let limiter = policy.build();
assert_eq!(limiter.max_requests(), 100);
assert_eq!(limiter.window_size_secs(), 60);
assert_eq!(limiter.eviction_grace_secs(), 60 * 2);
}
#[test]
fn it_builds_rate_limiter_with_eviction() {
let policy =
FixedWindow::new(100, Duration::from_secs(60)).with_eviction(Duration::from_secs(300));
let limiter = policy.build();
assert_eq!(limiter.max_requests(), 100);
assert_eq!(limiter.window_size_secs(), 60);
assert_eq!(limiter.eviction_grace_secs(), 300);
}
#[test]
fn it_creates_policy_with_zero_max_requests() {
let policy = FixedWindow::new(0, Duration::from_secs(60));
assert_eq!(policy.max_requests, 0);
}
#[test]
fn it_creates_policy_with_very_large_max_requests() {
let policy = FixedWindow::new(u32::MAX, Duration::from_secs(1));
assert_eq!(policy.max_requests, u32::MAX);
}
#[test]
fn it_creates_policy_with_zero_duration_window() {
let policy = FixedWindow::new(100, Duration::from_secs(0));
assert_eq!(policy.window_size, Duration::from_secs(0));
}
#[test]
fn it_creates_policy_with_subsecond_window() {
let policy = FixedWindow::new(100, Duration::from_millis(500));
assert_eq!(policy.window_size, Duration::from_millis(500));
}
#[test]
fn it_clones_policy_correctly() {
let original = FixedWindow::new(100, Duration::from_secs(60))
.with_name("original")
.with_eviction(Duration::from_secs(300));
let cloned = original.clone();
assert_eq!(cloned.max_requests, original.max_requests);
assert_eq!(cloned.window_size, original.window_size);
assert_eq!(cloned.name, original.name);
assert_eq!(cloned.eviction, original.eviction);
}
#[test]
fn it_creates_multiple_independent_policies() {
let policy1 = FixedWindow::new(100, Duration::from_secs(60)).with_name("policy1");
let policy2 = FixedWindow::new(200, Duration::from_secs(120)).with_name("policy2");
assert_eq!(policy1.max_requests, 100);
assert_eq!(policy2.max_requests, 200);
assert_eq!(policy1.name, Some("policy1".to_string()));
assert_eq!(policy2.name, Some("policy2".to_string()));
}
#[test]
fn it_overwrites_eviction_when_called_multiple_times() {
let policy = FixedWindow::new(100, Duration::from_secs(60))
.with_eviction(Duration::from_secs(300))
.with_eviction(Duration::from_secs(600));
assert_eq!(policy.eviction, Some(Duration::from_secs(600)));
}
#[test]
fn it_overwrites_name_when_called_multiple_times() {
let policy = FixedWindow::new(100, Duration::from_secs(60))
.with_name("first_name")
.with_name("second_name");
assert_eq!(policy.name, Some("second_name".to_string()));
}
}