use core::num::NonZeroU32;
use core::time::Duration;
use nonzero_ext::nonzero;
use crate::nanos::Nanos;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Quota {
pub(crate) max_burst: NonZeroU32,
pub(crate) replenish_1_per: Duration,
}
impl Quota {
pub const fn per_second(max_burst: NonZeroU32) -> Quota {
let replenish_interval_ns = Duration::from_secs(1).as_nanos() / (max_burst.get() as u128);
Quota {
max_burst,
replenish_1_per: Duration::from_nanos(replenish_interval_ns as u64),
}
}
pub const fn per_minute(max_burst: NonZeroU32) -> Quota {
let replenish_interval_ns = Duration::from_secs(60).as_nanos() / (max_burst.get() as u128);
Quota {
max_burst,
replenish_1_per: Duration::from_nanos(replenish_interval_ns as u64),
}
}
pub const fn per_hour(max_burst: NonZeroU32) -> Quota {
let replenish_interval_ns =
Duration::from_secs(60 * 60).as_nanos() / (max_burst.get() as u128);
Quota {
max_burst,
replenish_1_per: Duration::from_nanos(replenish_interval_ns as u64),
}
}
pub fn with_period(replenish_1_per: Duration) -> Option<Quota> {
if replenish_1_per.as_nanos() == 0 {
None
} else {
Some(Quota {
max_burst: nonzero!(1u32),
replenish_1_per,
})
}
}
pub const fn allow_burst(self, max_burst: NonZeroU32) -> Quota {
Quota { max_burst, ..self }
}
#[deprecated(
since = "0.2.0",
note = "This constructor is often confusing and non-intuitive. \
Use the `per_(interval)` / `with_period` and `max_burst` constructors instead."
)]
pub fn new(max_burst: NonZeroU32, replenish_all_per: Duration) -> Option<Quota> {
if replenish_all_per.as_nanos() == 0 {
None
} else {
Some(Quota {
max_burst,
replenish_1_per: replenish_all_per / max_burst.get(),
})
}
}
}
impl Quota {
pub const fn replenish_interval(&self) -> Duration {
self.replenish_1_per
}
pub const fn burst_size(&self) -> NonZeroU32 {
self.max_burst
}
pub const fn burst_size_replenished_in(&self) -> Duration {
let fill_in_ns = self.replenish_1_per.as_nanos() * self.max_burst.get() as u128;
Duration::from_nanos(fill_in_ns as u64)
}
}
impl Quota {
pub(crate) fn from_gcra_parameters(t: Nanos, tau: Nanos) -> Quota {
let max_burst =
unsafe { NonZeroU32::new_unchecked(1 + (tau.as_u64() / t.as_u64()) as u32) };
let replenish_1_per = t.into();
Quota {
max_burst,
replenish_1_per,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use nonzero_ext::nonzero;
#[test]
fn time_multiples() {
let hourly = Quota::per_hour(nonzero!(1u32));
let minutely = Quota::per_minute(nonzero!(1u32));
let secondly = Quota::per_second(nonzero!(1u32));
assert_eq!(
hourly.replenish_interval() / 60,
minutely.replenish_interval()
);
assert_eq!(
minutely.replenish_interval() / 60,
secondly.replenish_interval()
);
}
#[test]
fn period_error_cases() {
assert!(Quota::with_period(Duration::from_secs(0)).is_none());
#[allow(deprecated)]
{
assert!(Quota::new(nonzero!(1u32), Duration::from_secs(0)).is_none());
}
}
}