use axum::{
Json,
extract::{FromRequest, FromRequestParts, Query, Request},
http::{StatusCode, request::Parts},
response::{IntoResponse, Response},
};
use serde::{Serialize, de::DeserializeOwned};
use validator::Validate;
pub type HttpStatus = StatusCode;
#[derive(Debug, Clone, Copy)]
pub struct HttpError {
status: StatusCode,
public: Option<(&'static str, &'static str)>,
}
impl HttpError {
pub const fn new(status: StatusCode) -> Self {
let status = if status.as_u16() >= 400 && status.as_u16() <= 599 {
status
} else {
StatusCode::INTERNAL_SERVER_ERROR
};
Self {
status,
public: None,
}
}
pub fn public(status: StatusCode, code: &'static str, message: &'static str) -> Self {
let mut error = Self::new(status);
if status.is_client_error()
&& !code.is_empty()
&& code.len() <= 64
&& code
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
{
error.public = Some((code, message));
}
error
}
pub fn public_details(self) -> (&'static str, &'static str) {
self.public
.unwrap_or_else(|| crate::exceptions::default_error(self.status))
}
pub const fn status(self) -> StatusCode {
self.status
}
}
impl From<StatusCode> for HttpError {
fn from(status: StatusCode) -> Self {
Self::new(status)
}
}
impl std::fmt::Display for HttpError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "HTTP {}", self.status)
}
}
impl std::error::Error for HttpError {}
impl IntoResponse for HttpError {
fn into_response(self) -> Response {
crate::exceptions::error_response(self, "")
}
}
pub struct ValidatedJson<T>(pub T);
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
S: Send + Sync,
T: DeserializeOwned + Validate,
{
type Rejection = HttpError;
async fn from_request(request: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(value) = Json::<T>::from_request(request, state)
.await
.map_err(|error| HttpError::new(error.status()))?;
value
.validate()
.map_err(|_| HttpError::new(StatusCode::UNPROCESSABLE_ENTITY))?;
Ok(Self(value))
}
}
pub struct ValidatedQuery<T>(pub T);
impl<S, T> FromRequestParts<S> for ValidatedQuery<T>
where
S: Send + Sync,
T: DeserializeOwned + Validate,
{
type Rejection = HttpError;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let Query(value) = Query::<T>::from_request_parts(parts, state)
.await
.map_err(|error| HttpError::new(error.status()))?;
value
.validate()
.map_err(|_| HttpError::new(StatusCode::UNPROCESSABLE_ENTITY))?;
Ok(Self(value))
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ApiResponse {
pub status: u16,
pub description: &'static str,
pub json_type: Option<&'static str>,
pub schema: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ApiOperation {
pub method: &'static str,
pub path: String,
pub summary: &'static str,
pub tag: &'static str,
pub operation_id: &'static str,
pub body_type: Option<&'static str>,
pub body_schema: Option<serde_json::Value>,
pub query_schema: Option<serde_json::Value>,
pub responses: Vec<ApiResponse>,
}