use ::axum::{
Json,
extract::{FromRequest, Request},
http::{StatusCode, header::CONTENT_TYPE},
response::{IntoResponse, Response},
};
use ::garde::Validate;
#[derive(Debug, Clone, Copy)]
pub struct Validated<T>(pub T);
pub trait ValidatePayload {
type Inner: Validate<Context = ()>;
fn payload(&self) -> &Self::Inner;
}
impl<U> ValidatePayload for Json<U>
where
U: Validate<Context = ()>,
{
type Inner = U;
fn payload(&self) -> &U {
&self.0
}
}
impl<U> ValidatePayload for ::axum::Form<U>
where
U: Validate<Context = ()>,
{
type Inner = U;
fn payload(&self) -> &U {
&self.0
}
}
impl<U> ValidatePayload for ::axum::extract::Query<U>
where
U: Validate<Context = ()>,
{
type Inner = U;
fn payload(&self) -> &U {
&self.0
}
}
impl<U> ValidatePayload for ::axum::extract::Path<U>
where
U: Validate<Context = ()>,
{
type Inner = U;
fn payload(&self) -> &U {
&self.0
}
}
impl<S, T> FromRequest<S> for Validated<T>
where
S: Send + Sync,
T: FromRequest<S> + ValidatePayload + Send,
{
type Rejection = Response;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let extracted = T::from_request(req, state)
.await
.map_err(IntoResponse::into_response)?;
match extracted.payload().validate() {
Ok(()) => Ok(Self(extracted)),
Err(report) => Err(build_validation_response(&report)),
}
}
}
fn build_validation_response(report: &::garde::Report) -> Response {
let errors: Vec<::serde_json::Value> = report
.iter()
.map(|(path, err)| {
::serde_json::json!({
"path": path.to_string(),
"message": err.message(),
})
})
.collect();
let envelope = ::serde_json::json!({ "errors": errors });
let body = envelope.to_string();
let mut response = (StatusCode::UNPROCESSABLE_ENTITY, body).into_response();
response.headers_mut().insert(
CONTENT_TYPE,
"application/json".parse().expect("static value parses"),
);
response
}