mod bucket;
#[doc(inline)]
pub use bucket::TokenBucket;
#[cfg(feature = "std")]
mod limiter;
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[doc(inline)]
pub use limiter::{RateLimiter, RefundWait};
use core::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Rate {
units: u64,
per: Duration,
}
impl Rate {
#[must_use]
pub const fn new(units: u64, per: Duration) -> Self {
assert!(units != 0, "Rate: units must be non-zero");
assert!(
per.as_nanos() != 0 && per.as_nanos() <= u64::MAX as u128,
"Rate: per must be non-zero and at most u64::MAX nanos"
);
Self { units, per }
}
#[must_use]
pub const fn try_new(units: u64, per: Duration) -> Option<Self> {
if units == 0 || per.as_nanos() == 0 || per.as_nanos() > u64::MAX as u128 {
return None;
}
Some(Self { units, per })
}
#[must_use]
pub const fn per_sec(units: u64) -> Self {
Self::new(units, Duration::from_secs(1))
}
#[must_use]
pub const fn units(&self) -> u64 {
self.units
}
#[must_use]
pub const fn per(&self) -> Duration {
self.per
}
pub(crate) const fn per_nanos(&self) -> u64 {
self.per.as_nanos() as u64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Acquire {
Granted,
RetryAt(u64),
Never,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rate_valid() {
let rate = Rate::per_sec(10);
assert_eq!(rate.units(), 10);
assert_eq!(rate.per(), Duration::from_secs(1));
assert_eq!(rate.per_nanos(), 1_000_000_000);
}
#[test]
fn rate_try_new_invalid() {
assert!(Rate::try_new(0, Duration::from_secs(1)).is_none());
assert!(Rate::try_new(1, Duration::ZERO).is_none());
assert!(Rate::try_new(1, Duration::MAX).is_none());
assert!(Rate::try_new(1, Duration::from_nanos(1)).is_some());
}
#[test]
#[should_panic(expected = "non-zero")]
fn rate_new_zero_units_panics() {
assert_eq!(Rate::new(0, Duration::from_secs(1)).units(), 0);
}
}