Skip to main content

typeway_server/
handler.rs

1//! The [`Handler`] trait — connects async functions to HTTP routes via extractors.
2//!
3//! Handler is implemented for async functions of arities 0–16 via macro.
4//! Each function argument must implement [`FromRequestParts`] (metadata extractors)
5//! or [`FromRequest`] (body extractor, last argument only).
6
7use std::future::Future;
8use std::marker::PhantomData;
9use std::pin::Pin;
10
11use crate::body::BoxBody;
12use crate::extract::{FromRequest, FromRequestParts};
13use crate::response::IntoResponse;
14
15/// A handler that can process an HTTP request and produce a response.
16///
17/// Implemented automatically for async functions whose arguments are
18/// extractors. The `Args` type parameter encodes the extractor tuple
19/// and is used for trait resolution — users don't specify it directly.
20#[diagnostic::on_unimplemented(
21    message = "`{Self}` is not a valid handler for arguments `{Args}`",
22    label = "not a valid handler",
23    note = "handlers must be async functions whose arguments implement `FromRequestParts` or `FromRequest`"
24)]
25pub trait Handler<Args>: Clone + Send + Sync + 'static {
26    /// Call the handler with the given request parts and pre-collected body bytes.
27    fn call(
28        self,
29        parts: http::request::Parts,
30        body: bytes::Bytes,
31    ) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>>;
32}
33
34// ---------------------------------------------------------------------------
35// Arity 0: fn() -> Res
36// ---------------------------------------------------------------------------
37
38impl<F, Fut, Res> Handler<()> for F
39where
40    F: FnOnce() -> Fut + Clone + Send + Sync + 'static,
41    Fut: Future<Output = Res> + Send,
42    Res: IntoResponse,
43{
44    fn call(
45        self,
46        _parts: http::request::Parts,
47        _body: bytes::Bytes,
48    ) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>> {
49        Box::pin(async move { self().await.into_response() })
50    }
51}
52
53// ---------------------------------------------------------------------------
54// FromRequestParts-only arities (1–8)
55// ---------------------------------------------------------------------------
56
57macro_rules! impl_handler_from_parts {
58    ([$($T:ident),+], [$($t:ident),+]) => {
59        #[allow(non_snake_case)]
60        impl<F, Fut, Res, $($T,)+> Handler<($($T,)+)> for F
61        where
62            F: FnOnce($($T,)+) -> Fut + Clone + Send + Sync + 'static,
63            Fut: Future<Output = Res> + Send,
64            Res: IntoResponse,
65            $($T: FromRequestParts + 'static,)+
66        {
67            fn call(
68                self,
69                parts: http::request::Parts,
70                _body: bytes::Bytes,
71            ) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>> {
72                Box::pin(async move {
73                    $(
74                        let $t = match $T::from_request_parts(&parts) {
75                            Ok(v) => v,
76                            Err(e) => return e.into_response(),
77                        };
78                    )+
79                    self($($t,)+).await.into_response()
80                })
81            }
82        }
83    };
84}
85
86impl_handler_from_parts!([T1], [t1]);
87impl_handler_from_parts!([T1, T2], [t1, t2]);
88impl_handler_from_parts!([T1, T2, T3], [t1, t2, t3]);
89impl_handler_from_parts!([T1, T2, T3, T4], [t1, t2, t3, t4]);
90impl_handler_from_parts!([T1, T2, T3, T4, T5], [t1, t2, t3, t4, t5]);
91impl_handler_from_parts!([T1, T2, T3, T4, T5, T6], [t1, t2, t3, t4, t5, t6]);
92impl_handler_from_parts!([T1, T2, T3, T4, T5, T6, T7], [t1, t2, t3, t4, t5, t6, t7]);
93impl_handler_from_parts!(
94    [T1, T2, T3, T4, T5, T6, T7, T8],
95    [t1, t2, t3, t4, t5, t6, t7, t8]
96);
97
98// ---------------------------------------------------------------------------
99// Mixed: FromRequestParts args + one FromRequest body (last arg)
100// ---------------------------------------------------------------------------
101
102/// Marker type to distinguish "parts extractors + body extractor" from
103/// "all parts extractors" in Handler impls.
104pub struct WithBody<Parts, Body>(PhantomData<(Parts, Body)>);
105
106macro_rules! impl_handler_with_body {
107    // Special case: only a body extractor, no parts extractors
108    ([], []) => {
109        impl<F, Fut, Res, B> Handler<WithBody<(), B>> for F
110        where
111            F: FnOnce(B) -> Fut + Clone + Send + Sync + 'static,
112            Fut: Future<Output = Res> + Send,
113            Res: IntoResponse,
114            B: FromRequest + 'static,
115        {
116            fn call(
117                self,
118                parts: http::request::Parts,
119                body: bytes::Bytes,
120            ) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>> {
121                Box::pin(async move {
122                    let b = match B::from_request(&parts, body).await {
123                        Ok(v) => v,
124                        Err(e) => return e.into_response(),
125                    };
126                    self(b).await.into_response()
127                })
128            }
129        }
130    };
131    ([$($T:ident),+], [$($t:ident),+]) => {
132        #[allow(non_snake_case)]
133        impl<F, Fut, Res, $($T,)+ B> Handler<WithBody<($($T,)+), B>> for F
134        where
135            F: FnOnce($($T,)+ B) -> Fut + Clone + Send + Sync + 'static,
136            Fut: Future<Output = Res> + Send,
137            Res: IntoResponse,
138            $($T: FromRequestParts + 'static,)+
139            B: FromRequest + 'static,
140        {
141            fn call(
142                self,
143                parts: http::request::Parts,
144                body: bytes::Bytes,
145            ) -> Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>> {
146                Box::pin(async move {
147                    $(
148                        let $t = match $T::from_request_parts(&parts) {
149                            Ok(v) => v,
150                            Err(e) => return e.into_response(),
151                        };
152                    )+
153                    let b = match B::from_request(&parts, body).await {
154                        Ok(v) => v,
155                        Err(e) => return e.into_response(),
156                    };
157                    self($($t,)+ b).await.into_response()
158                })
159            }
160        }
161    };
162}
163
164impl_handler_with_body!([], []);
165impl_handler_with_body!([T1], [t1]);
166impl_handler_with_body!([T1, T2], [t1, t2]);
167impl_handler_with_body!([T1, T2, T3], [t1, t2, t3]);
168impl_handler_with_body!([T1, T2, T3, T4], [t1, t2, t3, t4]);
169impl_handler_with_body!([T1, T2, T3, T4, T5], [t1, t2, t3, t4, t5]);
170impl_handler_with_body!([T1, T2, T3, T4, T5, T6], [t1, t2, t3, t4, t5, t6]);
171impl_handler_with_body!([T1, T2, T3, T4, T5, T6, T7], [t1, t2, t3, t4, t5, t6, t7]);
172
173// ---------------------------------------------------------------------------
174// Type-erased handler for storage in the router
175// ---------------------------------------------------------------------------
176
177/// A pinned, boxed future producing an HTTP response.
178pub type ResponseFuture = Pin<Box<dyn Future<Output = http::Response<BoxBody>> + Send>>;
179
180/// A type-erased handler stored in the router.
181///
182/// Handlers receive pre-collected body bytes. This enables both Hyper
183/// and Axum body types to be collected at the router boundary before
184/// dispatch, avoiding body-type coupling in the handler infrastructure.
185///
186/// Uses `Arc` (rather than `Box`) so handlers can be cloned for dual
187/// registration (e.g., both REST and native gRPC dispatch).
188pub type BoxedHandler =
189    std::sync::Arc<dyn Fn(http::request::Parts, bytes::Bytes) -> ResponseFuture + Send + Sync>;
190
191/// Erase a handler's type for storage in the router.
192pub fn into_boxed_handler<H, Args>(handler: H) -> BoxedHandler
193where
194    H: Handler<Args>,
195    Args: 'static,
196{
197    std::sync::Arc::new(move |parts, body| {
198        let h = handler.clone();
199        h.call(parts, body)
200    })
201}