#[cfg(test)]
mod tests;
use std::collections::{HashMap, VecDeque};
use std::sync::{Mutex, OnceLock};
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::time::{Duration, Instant};
pub struct RateLimiter {
state: Mutex<HashMap<String, VecDeque<Instant>>>,
max_requests: AtomicU32,
window_secs: AtomicU64,
}
impl RateLimiter {
pub fn new(max_requests: u32, window_secs: u64) -> Self {
RateLimiter {
state: Mutex::new(HashMap::new()),
max_requests: AtomicU32::new(max_requests),
window_secs: AtomicU64::new(window_secs),
}
}
pub fn set_limits(&self, max_requests: u32, window_secs: u64) {
self.max_requests.store(max_requests, Ordering::Relaxed);
self.window_secs.store(window_secs, Ordering::Relaxed);
}
fn window(&self) -> Duration {
Duration::from_secs(self.window_secs.load(Ordering::Relaxed))
}
fn max(&self) -> u32 {
self.max_requests.load(Ordering::Relaxed)
}
pub fn check(&self, key: &str) -> bool {
let now = Instant::now();
let window = self.window();
let max = self.max();
let mut guard = self.state.lock().unwrap();
let timestamps = guard.entry(key.to_string()).or_default();
while timestamps.front().map(|t| now.duration_since(*t) > window).unwrap_or(false) {
timestamps.pop_front();
}
if (timestamps.len() as u32) < max {
timestamps.push_back(now);
true
} else {
false
}
}
pub fn remaining(&self, key: &str) -> u32 {
let now = Instant::now();
let window = self.window();
let max = self.max();
let mut guard = self.state.lock().unwrap();
let timestamps = guard.entry(key.to_string()).or_default();
while timestamps.front().map(|t| now.duration_since(*t) > window).unwrap_or(false) {
timestamps.pop_front();
}
max.saturating_sub(timestamps.len() as u32)
}
pub fn reset(&self, key: &str) {
self.state.lock().unwrap().remove(key);
}
}
static GLOBAL_LIMITER: OnceLock<RateLimiter> = OnceLock::new();
pub fn global() -> &'static RateLimiter {
GLOBAL_LIMITER.get_or_init(|| {
let max: u32 = std::env::var("RWS_CONFIG_RATE_LIMIT_MAX_REQUESTS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1000);
let window: u64 = std::env::var("RWS_CONFIG_RATE_LIMIT_WINDOW_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(60);
RateLimiter::new(max, window)
})
}