use std::collections::VecDeque;
use std::time::Duration;
use tokio::time::Instant;
#[derive(Debug)]
pub struct RateLimiter {
requests: VecDeque<Instant>,
max_requests: usize,
window: Duration,
}
impl RateLimiter {
pub fn new(max_requests: usize, window: Duration) -> Self {
Self {
requests: VecDeque::with_capacity(max_requests),
max_requests,
window,
}
}
pub fn default_payrix() -> Self {
Self::new(100, Duration::from_secs(60))
}
pub fn check(&mut self) -> Duration {
let now = Instant::now();
while let Some(&oldest) = self.requests.front() {
if now.duration_since(oldest) >= self.window {
self.requests.pop_front();
} else {
break;
}
}
if self.requests.len() < self.max_requests {
self.requests.push_back(now);
Duration::ZERO
} else {
let oldest = self.requests.front().expect("requests not empty at capacity");
let elapsed = now.duration_since(*oldest);
self.window - elapsed
}
}
#[allow(dead_code)]
pub fn record(&mut self) {
let now = Instant::now();
while let Some(&oldest) = self.requests.front() {
if now.duration_since(oldest) >= self.window {
self.requests.pop_front();
} else {
break;
}
}
self.requests.push_back(now);
}
#[allow(dead_code)]
pub fn current_count(&self) -> usize {
self.requests.len()
}
#[allow(dead_code)]
pub fn max_requests(&self) -> usize {
self.max_requests
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allows_requests_under_limit() {
let mut limiter = RateLimiter::new(3, Duration::from_secs(60));
assert_eq!(limiter.check(), Duration::ZERO);
assert_eq!(limiter.check(), Duration::ZERO);
assert_eq!(limiter.check(), Duration::ZERO);
assert_eq!(limiter.current_count(), 3);
}
#[test]
fn test_blocks_requests_at_limit() {
let mut limiter = RateLimiter::new(2, Duration::from_secs(60));
assert_eq!(limiter.check(), Duration::ZERO);
assert_eq!(limiter.check(), Duration::ZERO);
let wait = limiter.check();
assert!(wait > Duration::ZERO);
assert!(wait <= Duration::from_secs(60));
}
}