use crate::Error;
use tokio::time::{delay_for, Duration, Instant};
const DEFAULT_RATE_LIMIT: u16 = 60;
const DEFAULT_INTERVAL: Duration = Duration::from_secs(60);
#[derive(Copy, Clone, Debug, Default)]
pub struct RatelimitBuilder {
limit: Option<u16>,
interval: Option<Duration>,
}
impl RatelimitBuilder {
pub(crate) fn new() -> Self {
Self::default()
}
pub fn limit(mut self, limit: u16) -> Self {
self.limit = Some(limit);
self
}
pub fn interval(mut self, interval: Duration) -> Self {
self.interval = Some(interval);
self
}
pub(crate) fn build(self) -> Ratelimit {
let limit = self.limit.unwrap_or(DEFAULT_RATE_LIMIT);
Ratelimit {
limit,
remaining: limit,
interval: self.interval.unwrap_or(DEFAULT_INTERVAL),
reset_at: Instant::now(),
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Ratelimit {
limit: u16,
remaining: u16,
interval: Duration,
reset_at: Instant,
}
impl Ratelimit {
pub fn limit(&self) -> u16 {
self.limit
}
pub fn remaining(&self) -> u16 {
self.remaining
}
pub fn interval(&self) -> Duration {
self.interval
}
pub fn reset_at(&self) -> Instant {
self.reset_at
}
pub fn reset_in(&self) -> Option<Duration> {
self.reset_at.checked_duration_since(Instant::now())
}
pub(super) async fn pre_hook(&mut self) -> Result<(), Error> {
if self.limit == 0 {
return Ok(());
}
let delay = match self.reset_in() {
Some(delay) => delay,
None => {
self.reset();
self.remaining -= 1;
return Ok(());
}
};
if self.remaining == 0 {
delay_for(delay).await;
return Ok(());
}
self.remaining -= 1;
Ok(())
}
fn reset(&mut self) {
self.reset_at = Instant::now() + self.interval;
self.remaining = self.limit;
}
}
impl Default for Ratelimit {
fn default() -> Self {
RatelimitBuilder::new().build()
}
}
#[cfg(test)]
mod tests {
use crate::http::{Ratelimit, RatelimitBuilder};
use tokio::time::{Duration, Instant};
const NO_LIMIT_TEST_CAP: usize = 100;
#[tokio::test]
async fn test_default_ratelimit() {
let r = Ratelimit::default();
assert_eq!(r.limit(), 60);
assert_eq!(r.remaining(), 60);
assert_eq!(r.interval(), Duration::from_secs(60));
assert!(r.reset_in().is_none());
}
#[tokio::test]
async fn test_ratelimiting() {
let mut r = RatelimitBuilder::new()
.limit(3)
.interval(Duration::from_secs(3))
.build();
r.reset();
let reset_at = r.reset_at();
for i in 0..r.limit() {
r.pre_hook().await.unwrap();
assert_eq!(r.remaining(), r.limit() - i - 1);
}
assert_eq!(reset_at, r.reset_at());
r.pre_hook().await.unwrap();
assert!(Instant::now().checked_duration_since(reset_at).is_some());
r.pre_hook().await.unwrap();
assert_eq!(r.remaining(), r.limit() - 1);
assert!(r.reset_in().is_some());
}
#[tokio::test]
async fn test_disabled_ratelimiting() {
let mut r = RatelimitBuilder::new().limit(0).build();
r.reset();
let reset_at = r.reset_at();
for _ in 0..NO_LIMIT_TEST_CAP {
r.pre_hook().await.unwrap();
}
assert_eq!(reset_at, r.reset_at());
}
}