axum_extra/handler/mod.rs
1//! Additional handler utilities.
2
3use axum::body::Body;
4use axum::extract::Request;
5use axum::{
6 extract::FromRequest,
7 handler::Handler,
8 response::{IntoResponse, Response},
9};
10use futures_util::future::{BoxFuture, FutureExt, Map};
11use std::{future::Future, marker::PhantomData};
12
13mod or;
14
15pub use self::or::Or;
16
17/// Trait for async functions that can be used to handle requests.
18///
19/// This trait is similar to [`Handler`] but rather than taking the request it takes the extracted
20/// inputs.
21///
22/// The drawbacks of this trait is that you cannot apply middleware to individual handlers like you
23/// can with [`Handler::layer`].
24pub trait HandlerCallWithExtractors<T, S>: Sized {
25 /// The type of future calling this handler returns.
26 type Future: Future<Output = Response> + Send + 'static;
27
28 /// Call the handler with the extracted inputs.
29 fn call(self, extractors: T, state: S) -> <Self as HandlerCallWithExtractors<T, S>>::Future;
30
31 /// Convert this `HandlerCallWithExtractors` into [`Handler`].
32 fn into_handler(self) -> IntoHandler<Self, T, S> {
33 IntoHandler {
34 handler: self,
35 _marker: PhantomData,
36 }
37 }
38
39 /// Chain two handlers together, running the second one if the first one rejects.
40 ///
41 /// Note that this only moves to the next handler if an extractor fails. The response from
42 /// handlers are not considered.
43 ///
44 /// # Example
45 ///
46 /// ```
47 /// use axum_extra::handler::HandlerCallWithExtractors;
48 /// use axum::{
49 /// Router,
50 /// async_trait,
51 /// routing::get,
52 /// extract::FromRequestParts,
53 /// };
54 ///
55 /// // handlers for varying levels of access
56 /// async fn admin(admin: AdminPermissions) {
57 /// // request came from an admin
58 /// }
59 ///
60 /// async fn user(user: User) {
61 /// // we have a `User`
62 /// }
63 ///
64 /// async fn guest() {
65 /// // `AdminPermissions` and `User` failed, so we're just a guest
66 /// }
67 ///
68 /// // extractors for checking permissions
69 /// struct AdminPermissions {}
70 ///
71 /// #[async_trait]
72 /// impl<S> FromRequestParts<S> for AdminPermissions
73 /// where
74 /// S: Send + Sync,
75 /// {
76 /// // check for admin permissions...
77 /// # type Rejection = ();
78 /// # async fn from_request_parts(parts: &mut http::request::Parts, state: &S) -> Result<Self, Self::Rejection> {
79 /// # todo!()
80 /// # }
81 /// }
82 ///
83 /// struct User {}
84 ///
85 /// #[async_trait]
86 /// impl<S> FromRequestParts<S> for User
87 /// where
88 /// S: Send + Sync,
89 /// {
90 /// // check for a logged in user...
91 /// # type Rejection = ();
92 /// # async fn from_request_parts(parts: &mut http::request::Parts, state: &S) -> Result<Self, Self::Rejection> {
93 /// # todo!()
94 /// # }
95 /// }
96 ///
97 /// let app = Router::new().route(
98 /// "/users/:id",
99 /// get(
100 /// // first try `admin`, if that rejects run `user`, finally falling back
101 /// // to `guest`
102 /// admin.or(user).or(guest)
103 /// )
104 /// );
105 /// # let _: Router = app;
106 /// ```
107 fn or<R, Rt>(self, rhs: R) -> Or<Self, R, T, Rt, S>
108 where
109 R: HandlerCallWithExtractors<Rt, S>,
110 {
111 Or {
112 lhs: self,
113 rhs,
114 _marker: PhantomData,
115 }
116 }
117}
118
119macro_rules! impl_handler_call_with {
120 ( $($ty:ident),* $(,)? ) => {
121 #[allow(non_snake_case)]
122 impl<F, Fut, S, $($ty,)*> HandlerCallWithExtractors<($($ty,)*), S> for F
123 where
124 F: FnOnce($($ty,)*) -> Fut,
125 Fut: Future + Send + 'static,
126 Fut::Output: IntoResponse,
127 {
128 // this puts `futures_util` in our public API but thats fine in axum-extra
129 type Future = Map<Fut, fn(Fut::Output) -> Response>;
130
131 fn call(
132 self,
133 ($($ty,)*): ($($ty,)*),
134 _state: S,
135 ) -> <Self as HandlerCallWithExtractors<($($ty,)*), S>>::Future {
136 self($($ty,)*).map(IntoResponse::into_response)
137 }
138 }
139 };
140 }
141
142impl_handler_call_with!();
143impl_handler_call_with!(T1);
144impl_handler_call_with!(T1, T2);
145impl_handler_call_with!(T1, T2, T3);
146impl_handler_call_with!(T1, T2, T3, T4);
147impl_handler_call_with!(T1, T2, T3, T4, T5);
148impl_handler_call_with!(T1, T2, T3, T4, T5, T6);
149impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7);
150impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8);
151impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
152impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
153impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
154impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
155impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
156impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
157impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
158impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
159
160/// A [`Handler`] created from a [`HandlerCallWithExtractors`].
161///
162/// Created with [`HandlerCallWithExtractors::into_handler`].
163#[allow(missing_debug_implementations)]
164pub struct IntoHandler<H, T, S> {
165 handler: H,
166 _marker: PhantomData<fn() -> (T, S)>,
167}
168
169impl<H, T, S> Handler<T, S> for IntoHandler<H, T, S>
170where
171 H: HandlerCallWithExtractors<T, S> + Clone + Send + 'static,
172 T: FromRequest<S> + Send + 'static,
173 T::Rejection: Send,
174 S: Send + Sync + 'static,
175{
176 type Future = BoxFuture<'static, Response>;
177
178 fn call(self, req: Request, state: S) -> Self::Future {
179 let req = req.map(Body::new);
180 Box::pin(async move {
181 match T::from_request(req, &state).await {
182 Ok(t) => self.handler.call(t, state).await,
183 Err(rejection) => rejection.into_response(),
184 }
185 })
186 }
187}
188
189impl<H, T, S> Copy for IntoHandler<H, T, S> where H: Copy {}
190
191impl<H, T, S> Clone for IntoHandler<H, T, S>
192where
193 H: Clone,
194{
195 fn clone(&self) -> Self {
196 Self {
197 handler: self.handler.clone(),
198 _marker: self._marker,
199 }
200 }
201}