use anyhow::Result;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
#[derive(Clone)]
pub struct RateLimiter {
delay: Duration,
last_request: Arc<Mutex<Option<Instant>>>,
}
impl RateLimiter {
pub fn new(delay: Duration) -> Self {
Self {
delay,
last_request: Arc::new(Mutex::new(None)),
}
}
pub async fn wait(&self) {
let mut last = self.last_request.lock().await;
if let Some(last_time) = *last {
let elapsed = last_time.elapsed();
if elapsed < self.delay {
let sleep_duration = self.delay - elapsed;
tokio::time::sleep(sleep_duration).await;
}
}
*last = Some(Instant::now());
}
pub fn delay(&self) -> Duration {
self.delay
}
pub async fn reset(&self) {
let mut last = self.last_request.lock().await;
*last = None;
}
pub async fn time_until_next(&self) -> Duration {
let last = self.last_request.lock().await;
match *last {
Some(last_time) => {
let elapsed = last_time.elapsed();
if elapsed >= self.delay {
Duration::ZERO
} else {
self.delay - elapsed
}
}
None => Duration::ZERO,
}
}
}
pub fn parse_delay(s: &str) -> Result<Duration> {
let s = s.trim();
if let Some(value_str) = s.strip_suffix("ms") {
let ms: u64 = value_str.trim().parse()?;
return Ok(Duration::from_millis(ms));
}
if let Some(value_str) = s.strip_suffix('s') {
let seconds: f64 = value_str.trim().parse()?;
let millis = (seconds * 1000.0) as u64;
return Ok(Duration::from_millis(millis));
}
let ms: u64 = s.parse()?;
Ok(Duration::from_millis(ms))
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_no_delay_on_first_request() {
let limiter = RateLimiter::new(Duration::from_millis(100));
let start = Instant::now();
limiter.wait().await;
let elapsed = start.elapsed();
assert!(elapsed.as_millis() < 50);
}
#[tokio::test]
async fn test_delay_on_second_request() {
let limiter = RateLimiter::new(Duration::from_millis(100));
let start = Instant::now();
limiter.wait().await; limiter.wait().await;
let elapsed = start.elapsed();
assert!(elapsed.as_millis() >= 95 && elapsed.as_millis() < 200);
}
#[tokio::test]
async fn test_reset() {
let limiter = RateLimiter::new(Duration::from_millis(100));
limiter.wait().await;
limiter.reset().await;
let start = Instant::now();
limiter.wait().await; let elapsed = start.elapsed();
assert!(elapsed.as_millis() < 50);
}
#[tokio::test]
async fn test_time_until_next() {
let limiter = RateLimiter::new(Duration::from_millis(100));
assert_eq!(limiter.time_until_next().await, Duration::ZERO);
limiter.wait().await;
let wait_time = limiter.time_until_next().await;
assert!(wait_time.as_millis() > 50 && wait_time.as_millis() <= 100);
}
#[test]
fn test_parse_delay_milliseconds() {
assert_eq!(parse_delay("500ms").unwrap(), Duration::from_millis(500));
assert_eq!(parse_delay("1000ms").unwrap(), Duration::from_secs(1));
assert_eq!(parse_delay("0ms").unwrap(), Duration::ZERO);
}
#[test]
fn test_parse_delay_seconds() {
assert_eq!(parse_delay("1s").unwrap(), Duration::from_secs(1));
assert_eq!(parse_delay("2s").unwrap(), Duration::from_secs(2));
assert_eq!(parse_delay("0.5s").unwrap(), Duration::from_millis(500));
assert_eq!(parse_delay("1.5s").unwrap(), Duration::from_millis(1500));
}
#[test]
fn test_parse_delay_plain_number() {
assert_eq!(parse_delay("500").unwrap(), Duration::from_millis(500));
assert_eq!(parse_delay("1000").unwrap(), Duration::from_secs(1));
}
#[test]
fn test_parse_delay_with_whitespace() {
assert_eq!(
parse_delay(" 500ms ").unwrap(),
Duration::from_millis(500)
);
assert_eq!(parse_delay(" 2s ").unwrap(), Duration::from_secs(2));
}
#[test]
fn test_parse_delay_invalid() {
assert!(parse_delay("invalid").is_err());
assert!(parse_delay("abc ms").is_err());
assert!(parse_delay("").is_err());
}
}