use std::time::Duration;
use crate::quota::Nanos;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RateLimitInfo {
pub limit: u32,
pub remaining: u32,
pub reset_after: Duration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RateLimited {
pub limit: u32,
pub retry_after: Duration,
pub reset_after: Duration,
}
impl RateLimited {
pub fn retry_after_secs(&self) -> u64 {
ceil_secs(self.retry_after)
}
}
pub(crate) fn ceil_secs(duration: Duration) -> u64 {
let round_up = u64::from(duration.subsec_nanos() > 0);
duration.as_secs().saturating_add(round_up)
}
pub fn check_gcra(
tat: Option<Nanos>,
now: Nanos,
emission_interval: Nanos,
burst_offset: Nanos,
cost: u32,
) -> Result<(Nanos, RateLimitInfo), RateLimited> {
let limit = (burst_offset / emission_interval) as u32;
let current_tat = tat.unwrap_or(now).max(now);
let increment = emission_interval.saturating_mul(cost as Nanos);
let new_tat = current_tat.saturating_add(increment);
let allow_at = new_tat.saturating_sub(burst_offset);
if allow_at > now {
return Err(RateLimited {
limit,
retry_after: Duration::from_nanos(allow_at - now),
reset_after: Duration::from_nanos(current_tat - now),
});
}
let diff = burst_offset.saturating_sub(new_tat - now);
let remaining = (diff / emission_interval) as u32;
Ok((
new_tat,
RateLimitInfo {
limit,
remaining,
reset_after: Duration::from_nanos(new_tat - now),
},
))
}