Skip to main content

siderite_core/
handler.rs

1//! The [`Handler`] trait: async functions usable as endpoints.
2//!
3//! Implemented for `async fn`s and closures taking up to 12 extractor
4//! arguments. Every argument but the last implements
5//! [`FromRequestParts`]; the last implements [`FromRequest`] (and may read
6//! the body). The return type implements [`IntoResponse`].
7//!
8//! Because extraction, response conversion and documentation all go through
9//! siderite traits, a handler's OpenAPI operation is derived from its
10//! signature with no macros required.
11
12use crate::extract::{FromRequest, FromRequestParts, Request};
13use crate::response::{IntoResponse, Response};
14use siderite_openapi::{Operation, SchemaRegistry};
15use std::future::Future;
16
17/// An endpoint callable with a request. `T` is an inference marker.
18pub trait Handler<T>: Clone + Send + Sync + Sized + 'static {
19    /// Whether this handler needs request-owned background work.
20    const BACKGROUND_TASKS: bool = false;
21
22    /// Run extraction, the handler body and response conversion.
23    fn call(self, req: Request) -> impl Future<Output = Response> + Send;
24
25    /// Document the operation implied by the handler's signature.
26    fn describe(op: &mut Operation, registry: &mut SchemaRegistry);
27}
28
29#[allow(clippy::manual_async_fn)]
30impl<F, Fut, R> Handler<(R,)> for F
31where
32    F: FnOnce() -> Fut + Clone + Send + Sync + 'static,
33    Fut: Future<Output = R> + Send,
34    R: IntoResponse,
35{
36    fn call(self, _req: Request) -> impl Future<Output = Response> + Send {
37        async move { self().await.into_response() }
38    }
39
40    fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
41        R::describe(op, registry);
42    }
43}
44
45macro_rules! impl_handler {
46    ($($p:ident),* ; $last:ident) => {
47        #[allow(non_snake_case, unused_mut)]
48        impl<F, Fut, R, $($p,)* $last> Handler<(R, $($p,)* $last)> for F
49        where
50            F: FnOnce($($p,)* $last) -> Fut + Clone + Send + Sync + 'static,
51            Fut: Future<Output = R> + Send,
52            R: IntoResponse,
53            $($p: FromRequestParts,)*
54            $last: FromRequest,
55        {
56            const BACKGROUND_TASKS: bool =
57                false $(|| <$p as FromRequestParts>::BACKGROUND_TASKS)*
58                || <$last as FromRequest>::BACKGROUND_TASKS;
59
60            fn call(self, req: Request) -> impl Future<Output = Response> + Send {
61                async move {
62                    let (mut parts, body) = req.into_parts();
63                    $(
64                        let $p = match $p::from_request_parts(&mut parts).await {
65                            Ok(value) => value,
66                            Err(err) => return err.into_response(),
67                        };
68                    )*
69                    let req = Request::from_parts(parts, body);
70                    let $last = match $last::from_request(req).await {
71                        Ok(value) => value,
72                        Err(err) => return err.into_response(),
73                    };
74                    self($($p,)* $last).await.into_response()
75                }
76            }
77
78            fn describe(op: &mut Operation, registry: &mut SchemaRegistry) {
79                $(<$p as FromRequestParts>::describe(op, registry);)*
80                <$last as FromRequest>::describe(op, registry);
81                R::describe(op, registry);
82            }
83        }
84    };
85}
86
87impl_handler!(; A1);
88impl_handler!(A1; A2);
89impl_handler!(A1, A2; A3);
90impl_handler!(A1, A2, A3; A4);
91impl_handler!(A1, A2, A3, A4; A5);
92impl_handler!(A1, A2, A3, A4, A5; A6);
93impl_handler!(A1, A2, A3, A4, A5, A6; A7);
94impl_handler!(A1, A2, A3, A4, A5, A6, A7; A8);
95impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8; A9);
96impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9; A10);
97impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10; A11);
98impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11; A12);