Skip to main content

tuwunel_api/router/
handler.rs

1use std::{any::Any, fmt::Debug};
2
3use axum::{
4	Router,
5	body::Body,
6	extract::{FromRequest, FromRequestParts},
7	handler::Handler,
8	response::{IntoResponse, Response},
9	routing::{MethodFilter, on},
10};
11use futures::future::BoxFuture;
12use http::{Method, Request};
13use ruma::api::{IncomingRequest, path_builder::PathBuilder};
14use tuwunel_core::{Result, trace};
15
16use super::{Ruma, RumaResponse, State, auth::AuthDispatch};
17
18pub(in super::super) trait RumaHandler<T> {
19	fn add_route(&'static self, router: Router<State>, path: &str) -> Router<State>;
20	fn add_routes(&'static self, router: Router<State>) -> Router<State>;
21	fn call_route(handler: RouteHandler, state: State, request: Request<Body>) -> RouteResponse;
22}
23
24pub(in super::super) trait RouterExt {
25	fn ruma_route<H, T>(self, handler: &'static H) -> Self
26	where
27		H: RumaHandler<T>;
28}
29
30/// A route handler reduced to one fn-pointer shape: axum's generic routing
31/// stack instantiates once for this type instead of once per endpoint.
32#[derive(Clone, Copy)]
33struct Route {
34	call: RouteCall,
35	handler: RouteHandler,
36}
37
38type RouteCall = fn(RouteHandler, State, Request<Body>) -> RouteResponse;
39type RouteHandler = &'static (dyn Any + Send + Sync);
40type RouteResponse = BoxFuture<'static, Response>;
41
42impl RouterExt for Router<State> {
43	fn ruma_route<H, T>(self, handler: &'static H) -> Self
44	where
45		H: RumaHandler<T>,
46	{
47		handler.add_routes(self)
48	}
49}
50
51impl Handler<(), State> for Route {
52	type Future = RouteResponse;
53
54	fn call(self, request: Request<Body>, state: State) -> Self::Future {
55		(self.call)(self.handler, state, request)
56	}
57}
58
59macro_rules! ruma_handler {
60	( $($tx:ident),* $(,)? ) => {
61		#[allow(clippy::allow_attributes, non_snake_case)]
62		impl<Err, Req, Fut, Fun, $($tx,)*> RumaHandler<($($tx,)* Ruma<Req>,)> for Fun
63		where
64			Fun: Fn($($tx,)* Ruma<Req>,) -> Fut + Send + Sync + 'static,
65			Fut: Future<Output = Result<Req::OutgoingResponse, Err>> + Send,
66			Req: IncomingRequest + Debug + Send + Sync + 'static,
67			Req::Authentication: AuthDispatch,
68			Err: IntoResponse + Debug + Send,
69			<Req as IncomingRequest>::OutgoingResponse: Debug + Send,
70			$( $tx: FromRequestParts<State> + Send + Sync + 'static, )*
71		{
72			fn add_routes(&'static self, router: Router<State>) -> Router<State> {
73				Req::PATH_BUILDER
74					.all_paths()
75					.fold(router, |router, path| self.add_route(router, path))
76			}
77
78			fn add_route(&'static self, router: Router<State>, path: &str) -> Router<State> {
79				let route = Route { handler: self, call: Self::call_route };
80
81				router.route(path, on(method_to_filter(&Req::METHOD), route))
82			}
83
84			fn call_route(
85				handler: RouteHandler,
86				state: State,
87				request: Request<Body>,
88			) -> RouteResponse {
89				let handler: &'static Fun = handler
90					.downcast_ref()
91					.expect("route handler matches the type it registered with");
92
93				let response = async move {
94					#[allow(unused_mut)]
95					let (mut parts, body) = request.into_parts();
96					$(
97						let $tx = match $tx::from_request_parts(&mut parts, &state).await {
98							| Err(error) => return error.into_response(),
99							| Ok(value) => value,
100						};
101					)*
102
103					let request = Request::from_parts(parts, body);
104					let args = match Ruma::<Req>::from_request(request, &state).await {
105						| Err(error) => return error.into_response(),
106						| Ok(args) => args,
107					};
108
109					match handler($($tx,)* args).await.inspect(|response| trace!(?response)) {
110						| Err(error) => error.into_response(),
111						| Ok(response) => RumaResponse(response).into_response(),
112					}
113				};
114
115				Box::pin(response)
116			}
117		}
118	}
119}
120ruma_handler!();
121ruma_handler!(T1);
122ruma_handler!(T1, T2);
123ruma_handler!(T1, T2, T3);
124ruma_handler!(T1, T2, T3, T4);
125
126fn method_to_filter(method: &Method) -> MethodFilter {
127	match method {
128		| &Method::DELETE => MethodFilter::DELETE,
129		| &Method::GET => MethodFilter::GET,
130		| &Method::HEAD => MethodFilter::HEAD,
131		| &Method::OPTIONS => MethodFilter::OPTIONS,
132		| &Method::PATCH => MethodFilter::PATCH,
133		| &Method::POST => MethodFilter::POST,
134		| &Method::PUT => MethodFilter::PUT,
135		| &Method::TRACE => MethodFilter::TRACE,
136		| _ => panic!("Unsupported HTTP method"),
137	}
138}