use async_trait::async_trait;
use axum::http::request::Parts;
use axum::response::{IntoResponse, Response};
use serde_json::json;
#[derive(Debug, Clone)]
pub enum GuardError {
Unauthorized(String),
Forbidden(String),
TooManyRequests {
message: String,
retry_after_secs: u64,
},
}
impl GuardError {
pub fn unauthorized(message: impl Into<String>) -> Self {
Self::Unauthorized(message.into())
}
pub fn forbidden(message: impl Into<String>) -> Self {
Self::Forbidden(message.into())
}
pub fn too_many_requests(message: impl Into<String>, retry_after_secs: u64) -> Self {
Self::TooManyRequests {
message: message.into(),
retry_after_secs,
}
}
}
impl IntoResponse for GuardError {
fn into_response(self) -> Response {
match self {
GuardError::TooManyRequests {
message,
retry_after_secs,
} => {
let body = axum::Json(json!({
"statusCode": 429,
"message": message,
"error": "Too Many Requests",
}));
let mut resp = (axum::http::StatusCode::TOO_MANY_REQUESTS, body).into_response();
if let Ok(v) = retry_after_secs.to_string().parse() {
resp.headers_mut().insert("retry-after", v);
}
resp.headers_mut()
.insert("x-ratelimit-remaining", "0".parse().expect("static header"));
resp
}
other => {
let (status, message, error_label) = match other {
GuardError::Unauthorized(m) => {
(axum::http::StatusCode::UNAUTHORIZED, m, "Unauthorized")
}
GuardError::Forbidden(m) => (axum::http::StatusCode::FORBIDDEN, m, "Forbidden"),
GuardError::TooManyRequests { .. } => unreachable!(),
};
let body = axum::Json(json!({
"statusCode": status.as_u16(),
"message": message,
"error": error_label,
}));
(status, body).into_response()
}
}
}
}
#[async_trait]
pub trait CanActivate: Default + Send + Sync + 'static {
fn resolve(_registry: &crate::ProviderRegistry) -> Self
where
Self: Sized,
{
Self::default()
}
async fn can_activate(&self, parts: &Parts) -> Result<(), GuardError>;
}