use http::Response;
use http::header::HeaderValue;
#[derive(Debug, Clone)]
pub struct RateLimitHeaderValues {
pub limit: u64,
pub remaining: u64,
pub reset_at: u64,
pub retry_after: Option<u64>,
pub policy: String,
}
pub fn inject_rate_limit_headers<B>(
mut response: Response<B>,
values: &RateLimitHeaderValues,
) -> Response<B> {
if let Ok(header_value) = HeaderValue::from_str(&values.limit.to_string()) {
response
.headers_mut()
.insert("RateLimit-Limit", header_value);
}
if let Ok(header_value) = HeaderValue::from_str(&values.remaining.to_string()) {
response
.headers_mut()
.insert("RateLimit-Remaining", header_value);
}
if let Ok(header_value) = HeaderValue::from_str(&values.reset_at.to_string()) {
response
.headers_mut()
.insert("RateLimit-Reset", header_value);
}
if !values.policy.is_empty() {
if let Ok(header_value) = HeaderValue::from_str(&values.policy) {
response
.headers_mut()
.insert("RateLimit-Policy", header_value);
}
}
if let Some(retry_after) = values.retry_after {
if let Ok(header_value) = HeaderValue::from_str(&retry_after.to_string()) {
response.headers_mut().insert("Retry-After", header_value);
}
}
response
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inject_rate_limit_headers() {
let response = Response::new(());
let values = RateLimitHeaderValues {
limit: 100,
remaining: 99,
reset_at: 1234567890,
retry_after: None,
policy: "token_bucket".to_string(),
};
let response = inject_rate_limit_headers(response, &values);
assert_eq!(response.headers().get("RateLimit-Limit").unwrap(), "100");
assert_eq!(response.headers().get("RateLimit-Remaining").unwrap(), "99");
assert_eq!(
response.headers().get("RateLimit-Reset").unwrap(),
"1234567890"
);
assert_eq!(
response.headers().get("RateLimit-Policy").unwrap(),
"token_bucket"
);
assert!(response.headers().get("Retry-After").is_none());
}
#[test]
fn test_inject_rate_limit_headers_with_retry_after() {
let response = Response::new(());
let values = RateLimitHeaderValues {
limit: 100,
remaining: 0,
reset_at: 1234567890,
retry_after: Some(60),
policy: "fixed_window".to_string(),
};
let response = inject_rate_limit_headers(response, &values);
assert_eq!(response.headers().get("RateLimit-Limit").unwrap(), "100");
assert_eq!(response.headers().get("RateLimit-Remaining").unwrap(), "0");
assert_eq!(response.headers().get("Retry-After").unwrap(), "60");
}
#[test]
fn test_inject_rate_limit_headers_empty_policy() {
let response = Response::new(());
let values = RateLimitHeaderValues {
limit: 50,
remaining: 25,
reset_at: 999999999,
retry_after: None,
policy: String::new(),
};
let response = inject_rate_limit_headers(response, &values);
assert_eq!(response.headers().get("RateLimit-Limit").unwrap(), "50");
assert!(response.headers().get("RateLimit-Policy").is_none());
}
#[test]
fn test_inject_rate_limit_headers_invalid_policy_skipped() {
let response = Response::new(());
let values = RateLimitHeaderValues {
limit: 10,
remaining: 5,
reset_at: 1,
retry_after: None,
policy: "bad\nvalue".to_string(),
};
let response = inject_rate_limit_headers(response, &values);
assert_eq!(response.headers().get("RateLimit-Limit").unwrap(), "10");
assert!(response.headers().get("RateLimit-Policy").is_none());
}
#[test]
fn test_inject_rate_limit_headers_all_zero_values() {
let response = Response::new(());
let values = RateLimitHeaderValues {
limit: 0,
remaining: 0,
reset_at: 0,
retry_after: Some(0),
policy: "zero".to_string(),
};
let response = inject_rate_limit_headers(response, &values);
assert_eq!(response.headers().get("RateLimit-Limit").unwrap(), "0");
assert_eq!(response.headers().get("RateLimit-Remaining").unwrap(), "0");
assert_eq!(response.headers().get("RateLimit-Reset").unwrap(), "0");
assert_eq!(response.headers().get("Retry-After").unwrap(), "0");
}
}