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#[derive(Clone)]
23pub struct FormHandler<H, R> {
24 response_renderer: R,
25 _processor: PhantomData<H>,
26}
27
28#[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
54pub 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#[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}