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#[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}