use std::{future::Future, marker::PhantomData, pin::Pin};
use axum::{
extract::{FromRequest, FromRequestParts, Request},
handler::Handler,
response::{IntoResponse, Response},
routing::{get, MethodRouter},
};
use quokka_state::ProvideState;
use quokka_templating::Templating;
#[derive(Clone)]
pub struct FormHandler<H, R> {
response_renderer: R,
_processor: PhantomData<H>,
}
#[derive(Clone)]
pub struct JsonHandler<P, R> {
response_renderer: R,
_processor: PhantomData<P>,
}
#[derive(Clone, Debug, thiserror::Error)]
pub enum HandlerError<E> {
#[error("{0}")]
DataHandlerError(E),
#[error("Unable to extract data")]
DataExtractorRejection(crate::Error),
#[error("Unable to extract parts")]
ParamExtractorRejection(crate::Error),
}
pub trait DataHandler<S> {
type Args: FromRequestParts<S> + Send + Sync;
type Body: serde::de::DeserializeOwned + 'static;
type Error: std::error::Error + Send;
type Extension: Clone + Send + Sync + 'static;
fn process_data(
&self,
params: Self::Args,
body: Self::Body,
) -> impl Future<Output = Result<Self::Extension, Self::Error>> + Send;
}
impl<P, R> FormHandler<P, R> {
pub fn new(response_renderer: R) -> Self {
Self {
response_renderer,
_processor: PhantomData,
}
}
}
impl<P, R> JsonHandler<P, R> {
pub fn new(response_renderer: R) -> Self {
Self {
response_renderer,
_processor: PhantomData,
}
}
}
impl<S, H, R, T> Handler<T, S> for FormHandler<H, R>
where
Self: Send,
T: 'static,
S: Clone + Send + Sync + 'static,
S: ProvideState<Templating>,
S: ProvideState<H>,
H: DataHandler<S> + Clone + Send + Sync + 'static,
<<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync,
<H as DataHandler<S>>::Body: Send,
<H as DataHandler<S>>::Error: Clone + Send + Sync + 'static,
R: Clone + Send + Sync + 'static,
R: Handler<T, S>,
{
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
#[tracing::instrument(skip_all)]
fn call(self, request: Request, state: S) -> Self::Future {
let processor: H = state.provide();
let renderer = self.response_renderer.clone();
Box::pin(async move {
let (mut parts, body) = request.into_parts();
let request = Request::from_parts(parts.clone(), body);
match process_data_call::<_, _, axum::extract::Form<H::Body>>(
&processor, request, &state,
)
.await
{
Err(error) => {
tracing::error!(
?error,
handler = std::any::type_name::<H>(),
"Unable to process data with handler"
);
parts.extensions.insert(error);
}
Ok(extension) => {
parts.extensions.insert(extension);
}
}
R::call(renderer, Request::from_parts(parts, ().into()), state).await
})
}
}
impl<S, H, R> From<FormHandler<H, R>> for MethodRouter<S>
where
Self: Send,
S: Clone + Send + Sync + 'static,
S: ProvideState<Templating>,
S: ProvideState<H>,
H: DataHandler<S> + Clone + Send + Sync + 'static,
<<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync,
<H as DataHandler<S>>::Body: Send,
<H as DataHandler<S>>::Error: Clone + Send + Sync + 'static,
R: Clone + Send + Sync + 'static,
R: Handler<Response, S>,
{
fn from(val: FormHandler<H, R>) -> Self {
get::<_, Response, _>(val.response_renderer.clone()).post(val)
}
}
impl<S, H, R, T> Handler<T, S> for JsonHandler<H, R>
where
Self: Send,
T: 'static,
S: Clone + Send + Sync + 'static,
S: ProvideState<Templating>,
S: ProvideState<H>,
H: DataHandler<S> + Clone + Send + Sync + 'static,
<<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync,
<H as DataHandler<S>>::Body: Send,
<H as DataHandler<S>>::Error: Clone + Send + Sync + 'static,
R: Clone + Send + Sync + 'static,
R: Handler<T, S>,
{
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
#[tracing::instrument(skip_all)]
fn call(self, request: Request, state: S) -> Self::Future {
let processor: H = state.provide();
let renderer = self.response_renderer.clone();
Box::pin(async move {
let (mut parts, body) = request.into_parts();
let request = Request::from_parts(parts.clone(), body);
match process_data_call::<_, _, axum::extract::Json<H::Body>>(
&processor, request, &state,
)
.await
{
Err(error) => {
tracing::error!(
?error,
handler = std::any::type_name::<H>(),
"Unable to process data with handler"
);
parts.extensions.insert(error);
}
Ok(extension) => {
parts.extensions.insert(extension);
}
}
R::call(renderer, Request::from_parts(parts, ().into()), state).await
})
}
}
impl<S, H, R> From<JsonHandler<H, R>> for MethodRouter<S>
where
Self: Send,
S: Clone + Send + Sync + 'static,
S: ProvideState<Templating>,
S: ProvideState<H>,
H: DataHandler<S> + Clone + Send + Sync + 'static,
<<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync,
<H as DataHandler<S>>::Body: Send,
<H as DataHandler<S>>::Error: Clone + Send + Sync + 'static,
R: Clone + Send + Sync + 'static,
R: Handler<Response, S>,
{
fn from(val: JsonHandler<H, R>) -> Self {
get::<_, Response, _>(val.response_renderer.clone()).post(val)
}
}
trait DataExtractor<B, S> {
type Rejection: IntoResponse;
fn extract_data(
request: axum::extract::Request,
state: &S,
) -> impl Future<Output = Result<B, Self::Rejection>> + Send;
}
impl<B: serde::de::DeserializeOwned + Send + 'static, S: Send + Sync + 'static> DataExtractor<B, S>
for axum::extract::Form<B>
{
type Rejection = <axum::extract::Form<B> as FromRequest<S>>::Rejection;
async fn extract_data(
request: axum::extract::Request,
state: &S,
) -> Result<B, <axum::extract::Form<B> as FromRequest<S>>::Rejection> {
Self::from_request(request, state).await.map(|form| form.0)
}
}
impl<B: serde::de::DeserializeOwned + Send + 'static, S: Send + Sync + 'static> DataExtractor<B, S>
for axum::extract::Json<B>
{
type Rejection = <axum::extract::Json<B> as FromRequest<S>>::Rejection;
async fn extract_data(
request: axum::extract::Request,
state: &S,
) -> Result<B, <axum::extract::Json<B> as FromRequest<S>>::Rejection> {
Self::from_request(request, state).await.map(|form| form.0)
}
}
#[tracing::instrument(skip(handler, state))]
async fn process_data_call<H, S, X>(
handler: &H,
request: axum::extract::Request,
state: &S,
) -> Result<H::Extension, HandlerError<H::Error>>
where
H: Send + Sync + 'static,
H: DataHandler<S>,
S: Send + Sync + Clone + 'static,
<<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync + 'static,
X: DataExtractor<<H as DataHandler<S>>::Body, S>,
H::Body: Send,
{
let (mut parts, body) = request.into_parts();
let args = <H::Args as FromRequestParts<S>>::from_request_parts(&mut parts, state).await;
let args = match args {
Ok(args) => args,
Err(error) => {
let error = crate::Error::wrap_response(error).await;
tracing::error!(?error, "Unable to extract parts");
return Err(HandlerError::ParamExtractorRejection(error));
}
};
let parts2 = parts.clone();
let request = axum::extract::Request::from_parts(parts2, body);
let body = X::extract_data(request, state).await;
let body = match body {
Ok(body) => body,
Err(error) => {
let error = crate::Error::wrap_response(error).await;
tracing::error!(?error, "Unable to extract body");
return Err(HandlerError::DataExtractorRejection(error));
}
};
handler
.process_data(args, body)
.await
.map_err(HandlerError::DataHandlerError)
}