Skip to main content

typeway_server/
handler_for.rs

1//! Bridge between typed handlers and the runtime router.
2//!
3//! [`BoundHandler<E>`] pairs a type-erased handler with its endpoint metadata.
4//! The [`bind`] function captures the handler's `Args` at the call site (where
5//! type inference works) and erases them, leaving only the endpoint type `E`.
6
7use std::marker::PhantomData;
8
9use typeway_core::{Endpoint, ExtractPath, HttpMethod, PathSpec};
10
11use crate::handler::{into_boxed_handler, BoxedHandler, Handler};
12use crate::router::Router;
13
14/// A handler bound to a specific endpoint type, with the handler's arg types erased.
15///
16/// Created via the [`bind`] free function. The endpoint type `E` is preserved
17/// for compile-time API completeness checks via [`Serves`](crate::serves::Serves).
18pub struct BoundHandler<E> {
19    method: http::Method,
20    pattern: String,
21    match_fn: crate::router::MatchFn,
22    handler: BoxedHandler,
23    _endpoint: PhantomData<E>,
24}
25
26impl<E> BoundHandler<E> {
27    /// Create a new bound handler with explicit endpoint metadata.
28    pub fn new(
29        method: http::Method,
30        pattern: String,
31        match_fn: crate::router::MatchFn,
32        handler: BoxedHandler,
33    ) -> Self {
34        BoundHandler {
35            method,
36            pattern,
37            match_fn,
38            handler,
39            _endpoint: PhantomData,
40        }
41    }
42
43    /// Register this handler into the router.
44    pub(crate) fn register_into(self, router: &mut Router) {
45        router.add_route(self.method, self.pattern, self.match_fn, self.handler);
46    }
47}
48
49/// Bind a handler to an endpoint type, erasing the handler's arg types.
50///
51/// This is where type inference resolves the handler's `Args` parameter.
52/// After binding, only the endpoint type `E` remains.
53///
54/// # Example
55///
56/// ```ignore
57/// use typeway_server::bind;
58///
59/// let h = bind::<GetEndpoint<path!("hello"), String>, _, _>(hello_handler);
60/// ```
61pub fn bind<E, H, Args>(handler: H) -> BoundHandler<E>
62where
63    E: BindableEndpoint,
64    H: Handler<Args>,
65    Args: 'static,
66{
67    let method = E::method();
68    let pattern = E::pattern();
69    let match_fn = E::match_fn();
70
71    BoundHandler {
72        method,
73        pattern,
74        match_fn,
75        handler: into_boxed_handler(handler),
76        _endpoint: PhantomData,
77    }
78}
79
80/// Convenience macro to bind a handler to an endpoint without turbofish.
81///
82/// Instead of `bind::<_, _, _>(handler)`, write `bind!(handler)`.
83/// The endpoint type is inferred from the `Serves<API>` context.
84///
85/// ```ignore
86/// Server::<API>::new((
87///     bind!(hello),
88///     bind!(get_user),
89///     bind!(create_user),
90/// ));
91/// ```
92#[macro_export]
93macro_rules! bind {
94    ($handler:expr) => {
95        $crate::bind::<_, _, _>($handler)
96    };
97}
98
99/// Trait providing runtime endpoint metadata for binding.
100pub trait BindableEndpoint {
101    fn method() -> http::Method;
102    fn pattern() -> String;
103    fn match_fn() -> crate::router::MatchFn;
104}
105
106impl<M, P, Req, Res, Q, Err> BindableEndpoint for Endpoint<M, P, Req, Res, Q, Err>
107where
108    M: HttpMethod,
109    P: PathSpec + ExtractPath + Send + 'static,
110    P::Captures: Send,
111{
112    fn method() -> http::Method {
113        M::METHOD
114    }
115
116    fn pattern() -> String {
117        P::pattern()
118    }
119
120    fn match_fn() -> crate::router::MatchFn {
121        Box::new(|segments| P::extract(segments).is_some())
122    }
123}