use hyper::http;
pub struct Form<F>(pub F);
#[derive(Debug)]
#[derive(thiserror::Error)]
#[error("\n\t{}: {0}", std::any::type_name::<Self>())]
pub enum MissingFormError {
FormRejection(#[from] axum::extract::rejection::FormRejection),
JsonRejection(#[from] axum::extract::rejection::JsonRejection),
}
#[axum::async_trait]
impl<F, S, B> axum::extract::FromRequest<S, B> for Form<F>
where
F: serde::de::DeserializeOwned,
S: Send + Sync,
B: hyper::body::HttpBody + Send + 'static,
B::Data: Send,
B::Error: Into<axum::BoxError>,
{
type Rejection = MissingFormError;
async fn from_request(
req: axum::http::request::Request<B>,
state: &S,
) -> Result<Self, Self::Rejection> {
let f = if json_content_type(req.headers()) {
let axum::extract::Json(form) =
axum::extract::Json::<F>::from_request(req, state).await?;
form
} else {
let axum::extract::Form(form) =
axum::extract::Form::<F>::from_request(req, state).await?;
form
};
Ok(Self(f))
}
}
impl axum::response::IntoResponse for MissingFormError {
fn into_response(self) -> axum::response::Response {
let err_status = http::StatusCode::INTERNAL_SERVER_ERROR;
let err_body = self.to_string();
(err_status, err_body).into_response()
}
}
fn json_content_type(headers: &hyper::HeaderMap) -> bool {
let content_type =
if let Some(content_type) = headers.get(hyper::header::CONTENT_TYPE) {
content_type
} else {
return false;
};
let content_type = if let Ok(content_type) = content_type.to_str() {
content_type
} else {
return false;
};
let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {
mime
} else {
return false;
};
let is_json_content_type = mime.type_() == "application"
&& (mime.subtype() == "json"
|| mime.suffix().map_or(false, |name| name == "json"));
is_json_content_type
}