use std::sync::Mutex;
use std::time::{Duration, Instant};
use crate::envelope::DataCategory;
use super::LimitMap;
#[derive(Default)]
pub struct RateLimiter {
limits: Mutex<LimitMap>,
global: Mutex<Option<Instant>>,
}
impl RateLimiter {
pub fn new() -> Self {
RateLimiter::default()
}
pub fn is_limited(&self, category: DataCategory) -> bool {
let now = Instant::now();
if let Ok(g) = self.global.lock() {
if let Some(deadline) = *g {
if now < deadline {
return true;
}
}
}
if let Ok(map) = self.limits.lock() {
if let Some(deadline) = map.get(&category) {
return now < *deadline;
}
}
false
}
pub fn update_from_response(
&self,
category: DataCategory,
headers: &reqwest::header::HeaderMap,
) {
if let Some(val) = headers.get("retry-after").and_then(|v| v.to_str().ok()) {
if let Some(dur) = parse_retry_after(val) {
if let Ok(mut g) = self.global.lock() {
*g = Some(Instant::now() + dur);
}
return;
}
}
self.hold(category, Duration::from_secs(60));
}
pub fn hold(&self, category: DataCategory, dur: Duration) {
if let Ok(mut map) = self.limits.lock() {
map.insert(category, Instant::now() + dur);
}
}
}
fn parse_retry_after(value: &str) -> Option<Duration> {
let trimmed = value.trim();
if let Ok(secs) = trimmed.parse::<u64>() {
return Some(Duration::from_secs(secs));
}
Some(Duration::from_secs(60))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn delta_seconds_parsed() {
assert_eq!(parse_retry_after("30"), Some(Duration::from_secs(30)));
}
#[test]
fn http_date_falls_back() {
let d = parse_retry_after("Wed, 21 Oct 2026 07:28:00 GMT").unwrap();
assert_eq!(d, Duration::from_secs(60));
}
#[test]
fn hold_marks_category_limited() {
let rl = RateLimiter::new();
assert!(!rl.is_limited(DataCategory::Error));
rl.hold(DataCategory::Error, Duration::from_secs(60));
assert!(rl.is_limited(DataCategory::Error));
assert!(!rl.is_limited(DataCategory::Session));
}
#[test]
fn expired_hold_is_not_limited() {
let rl = RateLimiter::new();
rl.hold(DataCategory::Log, Duration::from_millis(0));
std::thread::sleep(Duration::from_millis(5));
assert!(!rl.is_limited(DataCategory::Log));
}
}