//! Minimal fixed-window rate limiter keyed by client identifier (IP). use std::collections::HashMap; use std::sync::Mutex; use std::time::{Duration, Instant}; pub struct RateLimiter { max: u32, window: Duration, hits: Mutex>, } impl RateLimiter { pub fn new(max: u32, window: Duration) -> Self { Self { max, window, hits: Mutex::new(HashMap::new()), } } /// Returns true if the request is allowed. pub fn check(&self, key: &str) -> bool { let mut hits = self.hits.lock().unwrap(); let now = Instant::now(); hits.retain(|_, (start, _)| now.duration_since(*start) < self.window); let entry = hits.entry(key.to_string()).or_insert((now, 0)); entry.1 += 1; entry.1 <= self.max } } #[cfg(test)] mod tests { use super::*; #[test] fn blocks_after_max_hits_per_key() { let l = RateLimiter::new(3, Duration::from_secs(60)); assert!(l.check("a") && l.check("a") && l.check("a")); assert!(!l.check("a")); assert!(l.check("b")); } }