use http::{HeaderValue, StatusCode, header::RETRY_AFTER};
use topcoat_core::{context::Cx, error::Result};
use crate::response::{IntoResponse, Response};
#[must_use]
pub fn too_many_requests(retry_after_secs: u64) -> TooManyRequestsError {
TooManyRequestsError::new(retry_after_secs)
}
#[derive(Debug)]
pub struct TooManyRequestsError {
retry_after_secs: u64,
}
impl TooManyRequestsError {
fn new(retry_after_secs: u64) -> Self {
Self { retry_after_secs }
}
#[must_use]
pub fn retry_after_secs(&self) -> u64 {
self.retry_after_secs
}
}
impl std::fmt::Display for TooManyRequestsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"too many requests (retry after {}s)",
self.retry_after_secs
)
}
}
impl std::error::Error for TooManyRequestsError {}
impl IntoResponse for TooManyRequestsError {
fn into_response(self, cx: &Cx) -> Result<Response> {
let mut response =
(StatusCode::TOO_MANY_REQUESTS, "too many requests").into_response(cx)?;
if let Ok(value) = HeaderValue::from_str(&self.retry_after_secs.to_string()) {
response.headers_mut().insert(RETRY_AFTER, value);
}
Ok(response)
}
}
#[cfg(test)]
mod tests {
use topcoat_core::context::Cx;
use super::*;
#[test]
fn responds_429_with_a_retry_after_header() {
let response = too_many_requests(60)
.into_response(&Cx::default())
.expect("the response builds");
assert_eq!(response.status(), StatusCode::TOO_MANY_REQUESTS);
assert_eq!(
response
.headers()
.get(RETRY_AFTER)
.map(HeaderValue::as_bytes),
Some(&b"60"[..])
);
}
#[test]
fn keeps_the_retry_after_it_was_built_with() {
assert_eq!(too_many_requests(30).retry_after_secs(), 30);
}
#[test]
fn names_the_caller_not_the_server() {
assert_eq!(
too_many_requests(5).to_string(),
"too many requests (retry after 5s)"
);
}
}