Skip to main content

jerrycan_core/
handler.rs

1//! Handler abstraction (spec ยง4.1): a handler is a plain async fn whose
2//! parameters implement [`FromRequest`] and whose return implements
3//! [`IntoResponse`]. Extraction failures short-circuit into error responses.
4
5use crate::extract::{FromRequest, RequestCtx};
6use crate::response::{IntoResponse, Response};
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10
11/// Type-erased handler as stored by the router.
12pub(crate) type BoxHandlerFn = Arc<
13    dyn for<'a> Fn(&'a mut RequestCtx) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>>
14        + Send
15        + Sync,
16>;
17
18/// Implemented for async fns of arity 0..=8 over [`FromRequest`] parameters.
19pub trait Handler<Args>: Send + Sync + 'static {
20    #[doc(hidden)]
21    fn into_handler_fn(self) -> BoxHandlerFn;
22}
23
24macro_rules! impl_handler {
25    ($($A:ident),*) => {
26        impl<F, Fut, R, $($A,)*> Handler<($($A,)*)> for F
27        where
28            F: Fn($($A),*) -> Fut + Clone + Send + Sync + 'static,
29            Fut: Future<Output = R> + Send,
30            R: IntoResponse,
31            $($A: FromRequest + 'static,)*
32        {
33            fn into_handler_fn(self) -> BoxHandlerFn {
34                #[allow(unused_variables)]
35                Arc::new(move |ctx: &mut RequestCtx| {
36                    let f = self.clone();
37                    Box::pin(async move {
38                        #[allow(non_snake_case, unused_variables)]
39                        {
40                            $(
41                                let $A = match <$A as FromRequest>::from_request(ctx).await {
42                                    Ok(v) => v,
43                                    Err(e) => return e.into_response(),
44                                };
45                            )*
46                            f($($A),*).await.into_response()
47                        }
48                    })
49                })
50            }
51        }
52    };
53}
54
55impl_handler!();
56impl_handler!(A1);
57impl_handler!(A1, A2);
58impl_handler!(A1, A2, A3);
59impl_handler!(A1, A2, A3, A4);
60impl_handler!(A1, A2, A3, A4, A5);
61impl_handler!(A1, A2, A3, A4, A5, A6);
62impl_handler!(A1, A2, A3, A4, A5, A6, A7);
63impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8);
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::Path;
69    use crate::dep::{Dep, DepEnv, DepResolver};
70    use crate::response::Json;
71    use std::collections::HashMap;
72
73    struct Greeting {
74        word: &'static str,
75    }
76
77    async fn greet(g: Dep<Greeting>, Path(id): Path<u32>) -> crate::Result<Json<String>> {
78        Ok(Json(format!("{} #{id}", g.word)))
79    }
80
81    #[tokio::test]
82    async fn handler_extracts_runs_and_responds() {
83        let mut env = DepEnv::default();
84        env.insert_value(Greeting { word: "hi" });
85        let req = http::Request::builder().uri("/greet/7").body(()).unwrap();
86        let (parts, ()) = req.into_parts();
87        let mut ctx = RequestCtx::new(
88            parts,
89            bytes::Bytes::new(),
90            DepResolver::new(
91                std::sync::Arc::new(env),
92                std::sync::Arc::new(HashMap::new()),
93            ),
94        );
95        ctx.params.push(("id".into(), "7".into()));
96
97        let h = greet.into_handler_fn();
98        let res = (*h)(&mut ctx).await; // explicit deref: Arc<dyn Fn> is not directly callable
99        assert_eq!(res.status(), http::StatusCode::OK);
100    }
101
102    #[tokio::test]
103    async fn extraction_failure_short_circuits_to_error_response() {
104        // No Greeting provider registered โ†’ Dep extraction fails โ†’ JC1001 โ†’ 500.
105        let req = http::Request::builder().uri("/greet/7").body(()).unwrap();
106        let (parts, ()) = req.into_parts();
107        let mut ctx = RequestCtx::new(
108            parts,
109            bytes::Bytes::new(),
110            DepResolver::new(Default::default(), std::sync::Arc::new(HashMap::new())),
111        );
112        ctx.params.push(("id".into(), "7".into()));
113
114        let h = greet.into_handler_fn();
115        let res = (*h)(&mut ctx).await;
116        assert_eq!(res.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
117    }
118}