use crate::AxumParts;
use alux_http::{FromPartsAlg, read_cookies, read_header_name};
use axum::body::Body;
use axum::extract::{FromRequest, FromRequestParts, Multipart, Path, Query, Request};
use axum::http::request::Parts;
use axum::http::{StatusCode, header};
use axum::response::{IntoResponse, Response};
use axum::{Form, Json};
use core::fmt::Display;
use core::future::Future;
use core::marker::PhantomData;
use serde::de::DeserializeOwned;
pub struct AxumPathInput<Input>(PhantomData<Input>);
pub struct AxumQueryInput<Input>(PhantomData<Input>);
pub struct AxumBodyInput<Input>(PhantomData<Input>);
pub struct AxumFormInput<Input>(PhantomData<Input>);
pub struct AxumRawBodyInput<Input>(PhantomData<Input>);
pub struct AxumPartsInput<Input>(PhantomData<Input>);
pub(crate) trait AxumInputAlg<Output> {
fn extract(parts: &mut Parts, body: &mut Option<Body>) -> impl Future<Output = Result<Output, Response>> + Send;
}
impl<Input> AxumInputAlg<Input> for AxumPartsInput<Input>
where
Input: FromRequestParts<()> + Send,
{
async fn extract(parts: &mut Parts, _body: &mut Option<Body>) -> Result<Input, Response> {
Input::from_request_parts(parts, &()).await.map_err(IntoResponse::into_response)
}
}
macro_rules! axum_parts_input {
($marker:ident, $extractor:ident) => {
impl<Input> AxumInputAlg<Input> for $marker<Input>
where
Input: DeserializeOwned + Send,
{
async fn extract(parts: &mut Parts, _body: &mut Option<Body>) -> Result<Input, Response> {
Ok($extractor::<Input>::from_request_parts(parts, &()).await.map_err(IntoResponse::into_response)?.0)
}
}
};
}
axum_parts_input!(AxumPathInput, Path);
axum_parts_input!(AxumQueryInput, Query);
fn taken(parts: &Parts, body: &mut Option<Body>) -> Request {
Request::from_parts(parts.clone(), body.take().unwrap_or_default())
}
macro_rules! axum_body_input {
($marker:ident, $extractor:ident) => {
impl<Input> AxumInputAlg<Input> for $marker<Input>
where
Input: DeserializeOwned + Send,
{
async fn extract(parts: &mut Parts, body: &mut Option<Body>) -> Result<Input, Response> {
let request = taken(parts, body);
Ok($extractor::<Input>::from_request(request, &()).await.map_err(IntoResponse::into_response)?.0)
}
}
};
}
axum_body_input!(AxumBodyInput, Json);
axum_body_input!(AxumFormInput, Form);
impl<Input> AxumInputAlg<Input> for AxumRawBodyInput<Input>
where
Input: FromRequest<()> + Send,
{
async fn extract(parts: &mut Parts, body: &mut Option<Body>) -> Result<Input, Response> {
let request = taken(parts, body);
Input::from_request(request, &()).await.map_err(IntoResponse::into_response)
}
}
pub struct AxumCookieInput<Input>(PhantomData<Input>);
pub struct AxumHeaderInput<Input>(PhantomData<Input>);
fn headers_of<Input>(stated: impl Iterator<Item = (String, String)>) -> Result<Input, String>
where
Input: DeserializeOwned,
{
let named = stated.map(|(name, value)| (read_header_name(&name), value)).collect::<Vec<_>>();
let encoded = serde_urlencoded::to_string(&named).map_err(|error| error.to_string())?;
serde_urlencoded::from_str(&encoded).map_err(|error| error.to_string())
}
fn cookies_of<Input>(header: Option<&str>) -> Result<Input, String>
where
Input: DeserializeOwned,
{
let stated = read_cookies(header.unwrap_or_default());
let encoded = serde_urlencoded::to_string(&stated).map_err(|error| error.to_string())?;
serde_urlencoded::from_str(&encoded).map_err(|error| error.to_string())
}
impl<Input> AxumInputAlg<Input> for AxumCookieInput<Input>
where
Input: DeserializeOwned + Send,
{
async fn extract(parts: &mut Parts, _body: &mut Option<Body>) -> Result<Input, Response> {
let header = parts.headers.get(header::COOKIE).and_then(|value| value.to_str().ok());
cookies_of(header).map_err(|error| (StatusCode::BAD_REQUEST, error).into_response())
}
}
impl<Input> AxumInputAlg<Input> for AxumHeaderInput<Input>
where
Input: DeserializeOwned + Send,
{
async fn extract(parts: &mut Parts, _body: &mut Option<Body>) -> Result<Input, Response> {
let stated =
parts.headers.iter().filter_map(|(name, value)| Some((name.to_string(), value.to_str().ok()?.to_owned())));
headers_of(stated).map_err(|error| (StatusCode::BAD_REQUEST, error).into_response())
}
}
pub struct AxumMultipartInput<Input>(PhantomData<Input>);
impl<Input> AxumInputAlg<Input> for AxumMultipartInput<Input>
where
Input: FromPartsAlg<AxumParts> + Send,
Input::Error: Display,
{
async fn extract(parts: &mut Parts, body: &mut Option<Body>) -> Result<Input, Response> {
let request = taken(parts, body);
let read = Multipart::from_request(request, &()).await.map_err(IntoResponse::into_response)?;
Input::from_parts(AxumParts(read))
.await
.map_err(|error| (StatusCode::BAD_REQUEST, error.to_string()).into_response())
}
}
pub(crate) trait AxumInputsAlg<Outputs> {
fn extract(parts: &mut Parts, body: &mut Option<Body>) -> impl Future<Output = Result<Outputs, Response>> + Send;
}
impl AxumInputsAlg<()> for () {
async fn extract(_parts: &mut Parts, _body: &mut Option<Body>) -> Result<(), Response> {
Ok(())
}
}
macro_rules! axum_inputs {
($($input:ident => $output:ident),+ $(,)?) => {
impl<$($input, $output),+> AxumInputsAlg<($($output,)+)> for ($($input,)+)
where
$($input: AxumInputAlg<$output>, $output: Send,)+
{
async fn extract(parts: &mut Parts, body: &mut Option<Body>) -> Result<($($output,)+), Response> {
Ok(($($input::extract(parts, body).await?,)+))
}
}
};
}
axum_inputs!(I1 => O1);
axum_inputs!(I1 => O1, I2 => O2);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12, I13 => O13);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12, I13 => O13, I14 => O14);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12, I13 => O13, I14 => O14, I15 => O15);
axum_inputs!(I1 => O1, I2 => O2, I3 => O3, I4 => O4, I5 => O5, I6 => O6, I7 => O7, I8 => O8, I9 => O9, I10 => O10, I11 => O11, I12 => O12, I13 => O13, I14 => O14, I15 => O15, I16 => O16);