You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
3.6 KiB
Rust

//! Admin rate limit model and request/response types.
//!
//! Per-provider rate limit configuration managed by admins.
//! Controls the maximum number of API requests allowed within
//! a sliding time window for each LLM provider.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// An admin rate limit record from the database.
#[derive(Debug, Clone, Serialize)]
pub struct AdminRateLimit {
pub id: Uuid,
pub provider_name: String,
pub max_requests: i32,
pub time_window_seconds: i32,
pub updated_at: DateTime<Utc>,
}
/// Request body for `PUT /api/v1/admin/rate-limits/:provider_name`.
#[derive(Debug, Deserialize)]
pub struct UpdateRateLimitRequest {
pub max_requests: i32,
pub time_window_seconds: i32,
}
impl UpdateRateLimitRequest {
/// Validate the rate limit update request.
///
/// - `max_requests` must be between 1 and 1000.
/// - `time_window_seconds` must be between 1 and 3600.
pub fn validate(&self) -> Result<(), String> {
if self.max_requests < 1 || self.max_requests > 1000 {
return Err("max_requests must be between 1 and 1000".into());
}
if self.time_window_seconds < 1 || self.time_window_seconds > 3600 {
return Err("time_window_seconds must be between 1 and 3600".into());
}
Ok(())
}
}
/// Response shape for rate limit endpoints.
#[derive(Debug, Serialize)]
pub struct RateLimitResponse {
pub id: Uuid,
pub provider_name: String,
pub max_requests: i32,
pub time_window_seconds: i32,
pub updated_at: DateTime<Utc>,
}
impl From<AdminRateLimit> for RateLimitResponse {
fn from(rl: AdminRateLimit) -> Self {
Self {
id: rl.id,
provider_name: rl.provider_name,
max_requests: rl.max_requests,
time_window_seconds: rl.time_window_seconds,
updated_at: rl.updated_at,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_rate_limit_request() {
let req = UpdateRateLimitRequest {
max_requests: 30,
time_window_seconds: 60,
};
assert!(req.validate().is_ok());
}
#[test]
fn test_max_requests_too_low() {
let req = UpdateRateLimitRequest {
max_requests: 0,
time_window_seconds: 60,
};
let err = req.validate().unwrap_err();
assert!(err.contains("max_requests"));
}
#[test]
fn test_max_requests_too_high() {
let req = UpdateRateLimitRequest {
max_requests: 1001,
time_window_seconds: 60,
};
let err = req.validate().unwrap_err();
assert!(err.contains("max_requests"));
}
#[test]
fn test_time_window_too_low() {
let req = UpdateRateLimitRequest {
max_requests: 30,
time_window_seconds: 0,
};
let err = req.validate().unwrap_err();
assert!(err.contains("time_window_seconds"));
}
#[test]
fn test_time_window_too_high() {
let req = UpdateRateLimitRequest {
max_requests: 30,
time_window_seconds: 3601,
};
let err = req.validate().unwrap_err();
assert!(err.contains("time_window_seconds"));
}
#[test]
fn test_boundary_values() {
let req_min = UpdateRateLimitRequest {
max_requests: 1,
time_window_seconds: 1,
};
assert!(req_min.validate().is_ok());
let req_max = UpdateRateLimitRequest {
max_requests: 1000,
time_window_seconds: 3600,
};
assert!(req_max.validate().is_ok());
}
}