#[cfg(test)]
mod tests;
use std::collections::{HashMap, VecDeque};
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant};
pub struct RateLimiter {
state: Mutex<HashMap<String, VecDeque<Instant>>>,
max_requests: u32,
window: Duration,
}
impl RateLimiter {
pub fn new(max_requests: u32, window_secs: u64) -> Self {
RateLimiter {
state: Mutex::new(HashMap::new()),
max_requests,
window: Duration::from_secs(window_secs),
}
}
pub fn check(&self, key: &str) -> bool {
let now = Instant::now();
let mut guard = self.state.lock().unwrap();
let window = self.window;
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) < self.max_requests {
timestamps.push_back(now);
true
} else {
false
}
}
pub fn remaining(&self, key: &str) -> u32 {
let now = Instant::now();
let mut guard = self.state.lock().unwrap();
let window = self.window;
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();
}
self.max_requests.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)
})
}