use std::sync::{Arc, Mutex};
use ijima_core::capabilities::intersection_number;
use schubert::PrincipalId;
use schubert::rate_limit::RateLimiter;
use crate::auth::AuthenticatedPrincipal;
pub type RateLimitState = Arc<Mutex<RateLimiter>>;
pub fn make_rate_limiter(base_tokens_per_second: f64, multiplier: f64) -> RateLimitState {
Arc::new(Mutex::new(RateLimiter::new(
base_tokens_per_second,
multiplier,
)))
}
pub fn consume(
state: &RateLimitState,
principal: &AuthenticatedPrincipal,
) -> schubert::error::Result<()> {
let mut rl = state.lock().expect("rate limiter poisoned");
let pid: PrincipalId = principal.principal.clone();
if rl.capacity(pid.clone()).is_none() {
let weight = intersection_number(&principal.capability);
rl.configure_principal(pid.clone(), weight);
}
rl.try_consume(pid).map(|_| ())
}
#[cfg(test)]
mod tests {
use super::*;
use ijima_core::capabilities::{ADMIN, MEMORY_READ, MEMORY_WRITE};
fn principal(cap: &str) -> AuthenticatedPrincipal {
AuthenticatedPrincipal {
principal: PrincipalId::new("elliott"),
capability: cap.to_string(),
}
}
#[test]
fn first_request_configures_and_consumes() {
let state = make_rate_limiter(1.0, 1.0);
let p = principal(MEMORY_READ);
assert!(consume(&state, &p).is_ok()); assert!(consume(&state, &p).is_err()); }
#[test]
fn higher_codimension_gets_more_capacity() {
let state = make_rate_limiter(5.0, 1.0);
let reader = AuthenticatedPrincipal {
principal: PrincipalId::new("reader"),
capability: MEMORY_READ.into(),
};
let writer = AuthenticatedPrincipal {
principal: PrincipalId::new("writer"),
capability: MEMORY_WRITE.into(),
};
for _ in 0..5 {
assert!(consume(&state, &reader).is_ok());
}
assert!(consume(&state, &reader).is_err());
for _ in 0..10 {
assert!(consume(&state, &writer).is_ok());
}
assert!(consume(&state, &writer).is_err());
}
#[test]
fn admin_gets_point_class_capacity() {
let state = make_rate_limiter(1.0, 1.0);
let admin = principal(ADMIN);
for _ in 0..16 {
assert!(consume(&state, &admin).is_ok());
}
assert!(consume(&state, &admin).is_err());
}
#[test]
fn multiplier_compresses_admin_ratio() {
let state = make_rate_limiter(10.0, 0.1);
let reader = AuthenticatedPrincipal {
principal: PrincipalId::new("reader"),
capability: MEMORY_READ.into(),
};
let admin = AuthenticatedPrincipal {
principal: PrincipalId::new("admin"),
capability: ADMIN.into(),
};
assert!(consume(&state, &reader).is_ok());
assert!(consume(&state, &reader).is_err());
for _ in 0..16 {
assert!(consume(&state, &admin).is_ok());
}
assert!(consume(&state, &admin).is_err());
}
}