pub use limiteron_macros::flow_control;
#[derive(Debug, Clone)]
pub struct FlowControlConfig {
pub rate: Option<RateLimit>,
pub quota: Option<QuotaLimit>,
pub concurrency: Option<u32>,
pub identifiers: Vec<String>,
pub on_exceed: String,
pub reject_message: String,
}
#[derive(Debug, Clone)]
pub struct RateLimit {
pub amount: u64,
pub unit: String,
}
#[derive(Debug, Clone)]
pub struct QuotaLimit {
pub max: u64,
pub period: String,
}
pub fn parse_rate_limit(rate_str: &str) -> Result<RateLimit, String> {
let parts: Vec<&str> = rate_str.split('/').collect();
if parts.len() != 2 {
return Err(format!(
"Invalid rate format: '{}', expected 'amount/unit' (e.g., '100/s')",
rate_str
));
}
let amount: u64 = parts[0]
.parse()
.map_err(|_| format!("Invalid rate amount: '{}'", parts[0]))?;
let unit = parts[1].to_lowercase();
if !["s", "m", "h"].contains(&unit.as_str()) {
return Err(format!(
"Invalid rate unit: '{}', expected one of: s, m, h",
unit
));
}
Ok(RateLimit { amount, unit })
}
pub fn parse_quota_limit(quota_str: &str) -> Result<QuotaLimit, String> {
let parts: Vec<&str> = quota_str.split('/').collect();
if parts.len() != 2 {
return Err(format!(
"Invalid quota format: '{}', expected 'max/period' (e.g., '1000/h')",
quota_str
));
}
let max: u64 = parts[0]
.parse()
.map_err(|_| format!("Invalid quota max: '{}'", parts[0]))?;
let period = parts[1].to_lowercase();
if !["s", "m", "h", "d"].contains(&period.as_str()) {
return Err(format!(
"Invalid quota period: '{}', expected one of: s, m, h, d",
period
));
}
Ok(QuotaLimit { max, period })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_rate_limit() {
let rate = parse_rate_limit("100/s").unwrap();
assert_eq!(rate.amount, 100);
assert_eq!(rate.unit, "s");
let rate = parse_rate_limit("50/m").unwrap();
assert_eq!(rate.amount, 50);
assert_eq!(rate.unit, "m");
let rate = parse_rate_limit("10/h").unwrap();
assert_eq!(rate.amount, 10);
assert_eq!(rate.unit, "h");
}
#[test]
fn test_parse_rate_limit_invalid() {
assert!(parse_rate_limit("invalid").is_err());
assert!(parse_rate_limit("100/x").is_err());
assert!(parse_rate_limit("abc/s").is_err());
}
#[test]
fn test_parse_rate_limit_units() {
let rate = parse_rate_limit("100/s").unwrap();
assert_eq!(rate.unit, "s");
let rate = parse_rate_limit("100/S").unwrap();
assert_eq!(rate.unit, "s");
let rate = parse_rate_limit("100/M").unwrap();
assert_eq!(rate.unit, "m");
let rate = parse_rate_limit("100/H").unwrap();
assert_eq!(rate.unit, "h");
}
#[test]
fn test_parse_rate_limit_edge_cases() {
let rate = parse_rate_limit("1/s").unwrap();
assert_eq!(rate.amount, 1);
let rate = parse_rate_limit("999999999999/s").unwrap();
assert_eq!(rate.amount, 999999999999);
}
#[test]
fn test_parse_quota_limit() {
let quota = parse_quota_limit("1000/h").unwrap();
assert_eq!(quota.max, 1000);
assert_eq!(quota.period, "h");
let quota = parse_quota_limit("10000/d").unwrap();
assert_eq!(quota.max, 10000);
assert_eq!(quota.period, "d");
}
#[test]
fn test_parse_quota_limit_invalid() {
assert!(parse_quota_limit("invalid").is_err());
assert!(parse_quota_limit("1000/x").is_err());
assert!(parse_quota_limit("abc/h").is_err());
}
#[test]
fn test_parse_quota_limit_periods() {
let quota = parse_quota_limit("100/s").unwrap();
assert_eq!(quota.period, "s");
let quota = parse_quota_limit("100/m").unwrap();
assert_eq!(quota.period, "m");
let quota = parse_quota_limit("100/h").unwrap();
assert_eq!(quota.period, "h");
let quota = parse_quota_limit("100/d").unwrap();
assert_eq!(quota.period, "d");
}
#[test]
fn test_flow_control_config_default() {
let config = FlowControlConfig {
rate: None,
quota: None,
concurrency: None,
identifiers: vec![],
on_exceed: "reject".to_string(),
reject_message: "Rate limit exceeded".to_string(),
};
assert!(config.rate.is_none());
assert!(config.quota.is_none());
assert!(config.concurrency.is_none());
assert!(config.identifiers.is_empty());
assert_eq!(config.on_exceed, "reject");
assert_eq!(config.reject_message, "Rate limit exceeded");
}
}