#![warn(missing_docs)]
use std::convert::Infallible;
use axum::{
async_trait,
body::HttpBody,
extract::{rejection::BytesRejection, FromRequest},
http::Request,
response::IntoResponse,
BoxError,
};
use serde::de::DeserializeOwned;
use thiserror::Error;
mod bind;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BindError {
#[error("invalid mime type")]
InvalidMimeType,
#[error("body read error: {0}")]
BodyReadError(BytesRejection),
#[cfg(feature = "json")]
#[error("json error: {0}")]
JsonError(serde_json::Error),
#[cfg(feature = "urlencoded")]
#[error("urlencoded error: {0}")]
UrlEncodedError(serde_urlencoded::de::Error),
#[cfg(feature = "xml")]
#[error("xml error: {0}")]
XmlError(serde_xml_rs::Error),
}
pub type BindResult<T> = Result<T, BindError>;
pub struct TryBindForm<T: DeserializeOwned>(pub BindResult<T>);
#[async_trait]
impl<S, B, T> FromRequest<S, B> for TryBindForm<T>
where
T: DeserializeOwned,
B: HttpBody + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
let deserialized = bind::bind_request(req, state).await;
Ok(TryBindForm(deserialized))
}
}
pub struct BindForm<T: DeserializeOwned>(pub T);
pub struct BindFormRejection(BindError);
impl IntoResponse for BindFormRejection {
fn into_response(self) -> axum::response::Response {
let body = format!("{}", self.0);
(
axum::http::StatusCode::BAD_REQUEST,
[(axum::http::header::CONTENT_TYPE, "text/plain")],
body,
)
.into_response()
}
}
#[async_trait]
impl<S, B, T> FromRequest<S, B> for BindForm<T>
where
T: DeserializeOwned,
B: HttpBody + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
S: Send + Sync,
{
type Rejection = BindFormRejection;
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
let deserialized = bind::bind_request(req, state).await;
match deserialized {
Ok(deserialized) => Ok(BindForm(deserialized)),
Err(err) => Err(BindFormRejection(err)),
}
}
}