use async_trait::async_trait;
use crate::CoolError;
#[derive(Debug, Clone, Copy)]
pub struct RateLimitConfig {
pub burst: u32,
pub refill_per_second: f64,
}
impl RateLimitConfig {
pub fn new(burst: u32, refill_per_second: f64) -> Self {
Self {
burst,
refill_per_second,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RateLimitDecision {
Allowed { remaining: u32 },
Throttled { retry_after_secs: u32 },
}
#[doc(hidden)]
pub fn _bucket_capacity_for(config: RateLimitConfig) -> u32 {
config.burst
}
#[async_trait]
pub trait RateLimitStore: Send + Sync + 'static {
async fn consume(
&self,
key: &str,
config: RateLimitConfig,
) -> Result<RateLimitDecision, CoolError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rate_limit_config_new_creates_correct_values() {
let config = RateLimitConfig::new(100, 10.5);
assert_eq!(config.burst, 100);
assert_eq!(config.refill_per_second, 10.5);
}
#[test]
fn rate_limit_decision_allowed_equality() {
let d1 = RateLimitDecision::Allowed { remaining: 42 };
let d2 = RateLimitDecision::Allowed { remaining: 42 };
assert_eq!(d1, d2);
}
#[test]
fn rate_limit_decision_throttled_equality() {
let d1 = RateLimitDecision::Throttled {
retry_after_secs: 5,
};
let d2 = RateLimitDecision::Throttled {
retry_after_secs: 5,
};
assert_eq!(d1, d2);
}
#[test]
fn bucket_capacity_for_returns_burst() {
let config = RateLimitConfig::new(42, 1.0);
assert_eq!(_bucket_capacity_for(config), 42);
}
}