use std::sync::Arc;
use std::time::Instant;
use dashmap::DashMap;
use uuid::Uuid;
use freshblu_core::error::FreshBluError;
pub struct RateLimiter {
windows: DashMap<Uuid, (u64, Instant)>,
max_requests: u64,
window_secs: u64,
}
impl RateLimiter {
pub fn new(max_requests: u64, window_secs: u64) -> Arc<Self> {
let limiter = Arc::new(Self {
windows: DashMap::new(),
max_requests,
window_secs,
});
let weak = Arc::downgrade(&limiter);
let window = window_secs;
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(300));
loop {
interval.tick().await;
match weak.upgrade() {
Some(limiter) => limiter.evict_stale(window),
None => break, }
}
});
limiter
}
pub fn check(&self, uuid: &Uuid) -> Result<(), FreshBluError> {
let now = Instant::now();
let mut entry = self.windows.entry(*uuid).or_insert((0, now));
let (count, window_start) = entry.value_mut();
if now.duration_since(*window_start).as_secs() >= self.window_secs {
*count = 0;
*window_start = now;
}
*count += 1;
if *count > self.max_requests {
return Err(FreshBluError::RateLimitExceeded);
}
Ok(())
}
fn evict_stale(&self, window_secs: u64) {
let now = Instant::now();
let threshold = window_secs * 2;
self.windows
.retain(|_, (_, start)| now.duration_since(*start).as_secs() < threshold);
}
pub fn tracked_count(&self) -> usize {
self.windows.len()
}
}