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
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());
|
|
}
|
|
}
|