use std::time::{Duration, Instant};
#[derive(Debug, PartialEq)]
pub enum RateLimitStatus {
NotStarted,
Expired,
Active,
}
#[derive(Debug, PartialEq)]
pub struct RateLimiter {
pub last_run: Option<Instant>,
pub min_time_threshold: Duration,
}
impl RateLimiter {
#[must_use]
pub fn new(min_time_threshold: Duration) -> Self {
Self {
last_run: None,
min_time_threshold,
}
}
pub fn get_status_and_update_last_run(&mut self, now: Instant) -> RateLimitStatus {
let status = self.get_status(now);
match status {
RateLimitStatus::NotStarted | RateLimitStatus::Expired => {
self.update_last_run(now);
}
RateLimitStatus::Active => {}
}
status
}
pub fn get_status(&mut self, now: Instant) -> RateLimitStatus {
match self.last_run {
None => RateLimitStatus::NotStarted,
Some(last_run) => {
if now.duration_since(last_run) > self.min_time_threshold {
RateLimitStatus::Expired
} else {
RateLimitStatus::Active
}
}
}
}
pub fn update_last_run(&mut self, now: Instant) { self.last_run.replace(now); }
}
#[cfg(test)]
mod tests {
use std::thread::sleep;
use super::*;
#[test]
fn test_not_started() {
let mut rate_limiter = RateLimiter::new(Duration::from_nanos(1));
assert_eq!(
rate_limiter.get_status(Instant::now()),
RateLimitStatus::NotStarted
);
}
#[test]
fn test_expired() {
let mut rate_limiter = RateLimiter::new(Duration::from_nanos(1));
let now = Instant::now();
rate_limiter.update_last_run(now - Duration::from_nanos(2));
assert_eq!(rate_limiter.get_status(now), RateLimitStatus::Expired);
}
#[test]
fn test_active() {
let mut rate_limiter = RateLimiter::new(Duration::from_nanos(1));
let now = Instant::now();
rate_limiter.update_last_run(now);
assert_eq!(rate_limiter.get_status(now), RateLimitStatus::Active);
}
#[test]
fn test_from_expired_to_active() {
let mut rate_limiter = RateLimiter::new(Duration::from_nanos(1));
let now = Instant::now();
rate_limiter.update_last_run(now - Duration::from_nanos(2));
assert_eq!(rate_limiter.get_status(now), RateLimitStatus::Expired);
rate_limiter.update_last_run(now);
assert_eq!(rate_limiter.get_status(now), RateLimitStatus::Active);
}
#[test]
fn test_active_to_expired() {
let mut rate_limiter = RateLimiter::new(Duration::from_nanos(1));
let now = Instant::now();
rate_limiter.update_last_run(now);
assert_eq!(rate_limiter.get_status(now), RateLimitStatus::Active);
sleep(Duration::from_nanos(2));
assert_eq!(
rate_limiter.get_status(Instant::now()),
RateLimitStatus::Expired
);
}
#[test]
fn test_get_status_and_update_last_run() {
let mut rate_limiter = RateLimiter::new(Duration::from_nanos(1));
let now = Instant::now();
assert_eq!(
rate_limiter.get_status_and_update_last_run(now),
RateLimitStatus::NotStarted
);
assert_eq!(rate_limiter.last_run, Some(now));
assert_eq!(
rate_limiter.get_status_and_update_last_run(now),
RateLimitStatus::Active
);
assert_eq!(rate_limiter.last_run, Some(now));
assert_eq!(
rate_limiter.get_status_and_update_last_run(now),
RateLimitStatus::Active
);
assert_eq!(rate_limiter.last_run, Some(now));
assert_eq!(
rate_limiter.get_status_and_update_last_run(now + Duration::from_nanos(2)),
RateLimitStatus::Expired
);
assert_eq!(rate_limiter.last_run, Some(now + Duration::from_nanos(2)));
assert_eq!(
rate_limiter.get_status_and_update_last_run(now + Duration::from_nanos(2)),
RateLimitStatus::Active
);
assert_eq!(rate_limiter.last_run, Some(now + Duration::from_nanos(2)));
}
}