use crossbeam_utils::CachePadded;
use governor::clock::{Clock, QuantaClock};
use std::sync::OnceLock;
pub use governor::Quota;
#[derive(Debug, Clone, Copy)]
pub struct StaticQuantaClock(&'static QuantaClock);
impl Default for StaticQuantaClock {
fn default() -> Self {
static CLOCK: CachePadded<OnceLock<QuantaClock>> = CachePadded::new(OnceLock::new());
Self(CLOCK.get_or_init(Default::default))
}
}
impl Clock for StaticQuantaClock {
type Instant = <QuantaClock as Clock>::Instant;
#[inline]
fn now(&self) -> Self::Instant {
Clock::now(self.0)
}
}
pub type DirectRateLimiter = governor::RateLimiter<
governor::state::NotKeyed,
governor::state::InMemoryState,
StaticQuantaClock,
governor::middleware::NoOpMiddleware<<StaticQuantaClock as Clock>::Instant>,
>;
#[macro_export]
#[doc(hidden)]
macro_rules! __ratelimit {
($limit:literal / s ; $expr:expr) => {
$crate::__ratelimit!(
$crate::ratelimit::Quota::per_second(::std::num::NonZeroU32::new($limit).unwrap());
$expr
)
};
($limit:literal / m ; $expr:expr) => {
$crate::__ratelimit!(
$crate::ratelimit::Quota::per_minute(::std::num::NonZeroU32::new($limit).unwrap());
$expr
)
};
($limit:literal / h ; $expr:expr) => {
$crate::__ratelimit!(
$crate::ratelimit::Quota::per_hour(::std::num::NonZeroU32::new($limit).unwrap());
$expr
)
};
($quota:expr ; $expr:expr) => {{
const QUOTA: $crate::ratelimit::Quota = $quota;
static LIMITER: ::std::sync::LazyLock<$crate::ratelimit::DirectRateLimiter> = ::std::sync::LazyLock::new(
|| $crate::ratelimit::DirectRateLimiter::direct_with_clock(QUOTA, ::std::default::Default::default())
);
if LIMITER.check().is_ok() {
$expr;
}
}};
}
pub use __ratelimit as ratelimit;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ratelimit() {
use governor::Quota;
use std::num::NonZeroU32;
const CUSTOM_QUOTA: Quota =
Quota::per_hour(NonZeroU32::new(60).unwrap()).allow_burst(NonZeroU32::new(20).unwrap());
let mut res_custom = 0;
for _ in 0..200 {
ratelimit!(CUSTOM_QUOTA; res_custom += 1);
}
assert_eq!(res_custom, 20);
let mut res_sec = 0;
for _ in 0..100 {
ratelimit!(20/s; res_sec += 1);
}
assert!(res_sec >= 20);
assert!(res_sec < 100);
let mut res_minute = 1;
for _ in 0..20 {
ratelimit!(3/m; res_minute *= 2);
}
assert_eq!(res_minute, 1 << 3);
let mut res_hour_a = 0;
let mut res_hour_b = 0;
for _ in 0..1000 {
ratelimit!(100/h; {
res_hour_a += 1;
res_hour_b += 2;
});
}
assert!(res_hour_a >= 100);
assert!(res_hour_a < 1000);
assert_eq!(res_hour_b, 2 * res_hour_a);
}
}