Skip to main content

quokka_handler/
processor.rs

1use std::{future::Future, marker::PhantomData, pin::Pin};
2
3use axum::{
4    extract::{FromRequest, FromRequestParts, Request},
5    handler::Handler,
6    response::{IntoResponse, Response},
7    routing::{get, MethodRouter},
8};
9use quokka_state::ProvideState;
10use quokka_templating::Templating;
11
12///
13/// Handles a POST request with the [axum::extract::Form] extractor and passes it to the provided [DataHandler] (in H).
14///
15/// The [Self::response_renderer] will be used for rendering a response, it will be provided with a [HandlerError]<[DataHandler::Error]> as
16/// an [axum::extract::Extension] so it can react to errors during handling. Additionally it will be provided with the
17/// [DataHandler::Extension] so that additional data can be used for communicating to the user.
18///
19/// **Note:** When using the `Into::into` call to convert this handler into a [MethodRouter] for axum, the [Self::response_renderer] will also
20/// be used for the [MethodRouter::get] call
21///
22#[derive(Clone)]
23pub struct FormHandler<H, R> {
24    response_renderer: R,
25    _processor: PhantomData<H>,
26}
27
28///
29/// Handles a POST request with the [axum::extract::Json] extractor and passes it to the provided [DataHandler] (in H).
30///
31/// The [Self::response_renderer] will be used for rendering a response, it will be provided with a [HandlerError]<[DataHandler::Error]> as
32/// an [axum::extract::Extension] so it can react to errors during handling. Additionally it will be provided with the
33/// [DataHandler::Extension] so that additional data can be used for communicating to the user.
34///
35/// **Note:** When using the `Into::into` call to convert this handler into a [MethodRouter] for axum, the [Self::response_renderer] will also
36/// be used for the [MethodRouter::get] call
37///
38#[derive(Clone)]
39pub struct JsonHandler<P, R> {
40    response_renderer: R,
41    _processor: PhantomData<P>,
42}
43
44#[derive(Clone, Debug, thiserror::Error)]
45pub enum HandlerError<E> {
46    #[error("{0}")]
47    DataHandlerError(E),
48    #[error("Unable to extract data")]
49    DataExtractorRejection(crate::Error),
50    #[error("Unable to extract parts")]
51    ParamExtractorRejection(crate::Error),
52}
53
54///
55/// Handles incoming data
56///
57/// - [Self::Args] - This can be used to receive Args from the request using anything that is [axum::extract::FromRequestParts]
58/// - [Self::Body] - The data which should be received through the request. This is supposed to be the struct, not the axum extractor.
59/// - [Self::Error] - The error type which gets emitted when something fails
60/// - [Self::Extension] - Anything that can contain additional data to indicate success (like a message)
61///
62pub trait DataHandler<S> {
63    type Args: FromRequestParts<S> + Send + Sync;
64    type Body: serde::de::DeserializeOwned + 'static;
65    type Error: std::error::Error + Send;
66    type Extension: Clone + Send + Sync + 'static;
67
68    fn process_data(
69        &self,
70        params: Self::Args,
71        body: Self::Body,
72    ) -> impl Future<Output = Result<Self::Extension, Self::Error>> + Send;
73}
74
75impl<P, R> FormHandler<P, R> {
76    pub fn new(response_renderer: R) -> Self {
77        Self {
78            response_renderer,
79            _processor: PhantomData,
80        }
81    }
82}
83
84impl<P, R> JsonHandler<P, R> {
85    pub fn new(response_renderer: R) -> Self {
86        Self {
87            response_renderer,
88            _processor: PhantomData,
89        }
90    }
91}
92
93impl<S, H, R, T> Handler<T, S> for FormHandler<H, R>
94where
95    Self: Send,
96    T: 'static,
97    S: Clone + Send + Sync + 'static,
98    S: ProvideState<Templating>,
99    S: ProvideState<H>,
100    H: DataHandler<S> + Clone + Send + Sync + 'static,
101    <<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync,
102    <H as DataHandler<S>>::Body: Send,
103    <H as DataHandler<S>>::Error: Clone + Send + Sync + 'static,
104    R: Clone + Send + Sync + 'static,
105    R: Handler<T, S>,
106{
107    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
108
109    #[tracing::instrument(skip_all)]
110    fn call(self, request: Request, state: S) -> Self::Future {
111        let processor: H = state.provide();
112        let renderer = self.response_renderer.clone();
113
114        Box::pin(async move {
115            let (mut parts, body) = request.into_parts();
116            let request = Request::from_parts(parts.clone(), body);
117
118            match process_data_call::<_, _, axum::extract::Form<H::Body>>(
119                &processor, request, &state,
120            )
121            .await
122            {
123                Err(error) => {
124                    tracing::error!(
125                        ?error,
126                        handler = std::any::type_name::<H>(),
127                        "Unable to process data with handler"
128                    );
129                    parts.extensions.insert(error);
130                }
131                Ok(extension) => {
132                    parts.extensions.insert(extension);
133                }
134            }
135
136            R::call(renderer, Request::from_parts(parts, ().into()), state).await
137        })
138    }
139}
140
141impl<S, H, R> From<FormHandler<H, R>> for MethodRouter<S>
142where
143    Self: Send,
144    S: Clone + Send + Sync + 'static,
145    S: ProvideState<Templating>,
146    S: ProvideState<H>,
147    H: DataHandler<S> + Clone + Send + Sync + 'static,
148    <<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync,
149    <H as DataHandler<S>>::Body: Send,
150    <H as DataHandler<S>>::Error: Clone + Send + Sync + 'static,
151    R: Clone + Send + Sync + 'static,
152    R: Handler<Response, S>,
153{
154    fn from(val: FormHandler<H, R>) -> Self {
155        get::<_, Response, _>(val.response_renderer.clone()).post(val)
156    }
157}
158
159impl<S, H, R, T> Handler<T, S> for JsonHandler<H, R>
160where
161    Self: Send,
162    T: 'static,
163    S: Clone + Send + Sync + 'static,
164    S: ProvideState<Templating>,
165    S: ProvideState<H>,
166    H: DataHandler<S> + Clone + Send + Sync + 'static,
167    <<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync,
168    <H as DataHandler<S>>::Body: Send,
169    <H as DataHandler<S>>::Error: Clone + Send + Sync + 'static,
170    R: Clone + Send + Sync + 'static,
171    R: Handler<T, S>,
172{
173    type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
174
175    #[tracing::instrument(skip_all)]
176    fn call(self, request: Request, state: S) -> Self::Future {
177        let processor: H = state.provide();
178        let renderer = self.response_renderer.clone();
179
180        Box::pin(async move {
181            let (mut parts, body) = request.into_parts();
182            let request = Request::from_parts(parts.clone(), body);
183
184            match process_data_call::<_, _, axum::extract::Json<H::Body>>(
185                &processor, request, &state,
186            )
187            .await
188            {
189                Err(error) => {
190                    tracing::error!(
191                        ?error,
192                        handler = std::any::type_name::<H>(),
193                        "Unable to process data with handler"
194                    );
195                    parts.extensions.insert(error);
196                }
197                Ok(extension) => {
198                    parts.extensions.insert(extension);
199                }
200            }
201
202            R::call(renderer, Request::from_parts(parts, ().into()), state).await
203        })
204    }
205}
206
207impl<S, H, R> From<JsonHandler<H, R>> for MethodRouter<S>
208where
209    Self: Send,
210    S: Clone + Send + Sync + 'static,
211    S: ProvideState<Templating>,
212    S: ProvideState<H>,
213    H: DataHandler<S> + Clone + Send + Sync + 'static,
214    <<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync,
215    <H as DataHandler<S>>::Body: Send,
216    <H as DataHandler<S>>::Error: Clone + Send + Sync + 'static,
217    R: Clone + Send + Sync + 'static,
218    R: Handler<Response, S>,
219{
220    fn from(val: JsonHandler<H, R>) -> Self {
221        get::<_, Response, _>(val.response_renderer.clone()).post(val)
222    }
223}
224
225trait DataExtractor<B, S> {
226    type Rejection: IntoResponse;
227
228    fn extract_data(
229        request: axum::extract::Request,
230        state: &S,
231    ) -> impl Future<Output = Result<B, Self::Rejection>> + Send;
232}
233
234impl<B: serde::de::DeserializeOwned + Send + 'static, S: Send + Sync + 'static> DataExtractor<B, S>
235    for axum::extract::Form<B>
236{
237    type Rejection = <axum::extract::Form<B> as FromRequest<S>>::Rejection;
238
239    async fn extract_data(
240        request: axum::extract::Request,
241        state: &S,
242    ) -> Result<B, <axum::extract::Form<B> as FromRequest<S>>::Rejection> {
243        Self::from_request(request, state).await.map(|form| form.0)
244    }
245}
246
247impl<B: serde::de::DeserializeOwned + Send + 'static, S: Send + Sync + 'static> DataExtractor<B, S>
248    for axum::extract::Json<B>
249{
250    type Rejection = <axum::extract::Json<B> as FromRequest<S>>::Rejection;
251
252    async fn extract_data(
253        request: axum::extract::Request,
254        state: &S,
255    ) -> Result<B, <axum::extract::Json<B> as FromRequest<S>>::Rejection> {
256        Self::from_request(request, state).await.map(|form| form.0)
257    }
258}
259
260///
261/// # Generics
262///
263/// - H: The handler for the data [DataHandler]
264/// - S: The app state
265/// - X: The eXtractor used to get the [DataHandler::Body] from the [Request]
266///
267#[tracing::instrument(skip(handler, state))]
268async fn process_data_call<H, S, X>(
269    handler: &H,
270    request: axum::extract::Request,
271    state: &S,
272) -> Result<H::Extension, HandlerError<H::Error>>
273where
274    H: Send + Sync + 'static,
275    H: DataHandler<S>,
276    S: Send + Sync + Clone + 'static,
277    <<H as DataHandler<S>>::Args as FromRequestParts<S>>::Rejection: Send + Sync + 'static,
278    X: DataExtractor<<H as DataHandler<S>>::Body, S>,
279    H::Body: Send,
280{
281    let (mut parts, body) = request.into_parts();
282    let args = <H::Args as FromRequestParts<S>>::from_request_parts(&mut parts, state).await;
283
284    let args = match args {
285        Ok(args) => args,
286        Err(error) => {
287            let error = crate::Error::wrap_response(error).await;
288
289            tracing::error!(?error, "Unable to extract parts");
290
291            return Err(HandlerError::ParamExtractorRejection(error));
292        }
293    };
294    let parts2 = parts.clone();
295    let request = axum::extract::Request::from_parts(parts2, body);
296    let body = X::extract_data(request, state).await;
297    let body = match body {
298        Ok(body) => body,
299        Err(error) => {
300            let error = crate::Error::wrap_response(error).await;
301
302            tracing::error!(?error, "Unable to extract body");
303
304            return Err(HandlerError::DataExtractorRejection(error));
305        }
306    };
307
308    handler
309        .process_data(args, body)
310        .await
311        .map_err(HandlerError::DataHandlerError)
312}