Skip to main content

serverkit/
route.rs

1use std::{any::TypeId, marker::PhantomData};
2
3use crate::{Handler, Method, Middleware, Router, middleware::MiddlewareEntry, openapi::Operation};
4
5type OperationModifier = Box<dyn FnOnce(&mut Operation)>;
6
7pub struct Route<H, Arguments, Input> {
8    path: &'static str,
9    method: Method,
10    handler: H,
11    operation_modifiers: Vec<OperationModifier>,
12    middlewares: Vec<MiddlewareEntry>,
13    excluded_middlewares: Vec<TypeId>,
14    signature: PhantomData<fn() -> (Arguments, Input)>,
15}
16
17impl<H, Arguments, Input> Route<H, Arguments, Input> {
18    pub fn summary(self, summary: impl Into<String>) -> Self {
19        let summary = summary.into();
20        self.openapi(move |operation| {
21            operation.summary(summary);
22        })
23    }
24
25    pub fn description(self, description: impl Into<String>) -> Self {
26        let description = description.into();
27        self.openapi(move |operation| {
28            operation.description(description);
29        })
30    }
31
32    pub fn tag(self, tag: impl Into<String>) -> Self {
33        let tag = tag.into();
34        self.openapi(move |operation| {
35            operation.tag(tag);
36        })
37    }
38
39    pub fn operation_id(self, operation_id: impl Into<String>) -> Self {
40        let operation_id = operation_id.into();
41        self.openapi(move |operation| {
42            operation.operation_id(operation_id);
43        })
44    }
45
46    pub fn openapi(mut self, modifier: impl FnOnce(&mut Operation) + 'static) -> Self {
47        self.operation_modifiers.push(Box::new(modifier));
48        self
49    }
50
51    pub fn middleware<M: Middleware>(mut self, middleware: M) -> Self {
52        self.middlewares.push(MiddlewareEntry::new(middleware));
53        self
54    }
55
56    pub fn without_middleware<M: Middleware>(mut self) -> Self {
57        let type_id = TypeId::of::<M>();
58        if !self.excluded_middlewares.contains(&type_id) {
59            self.excluded_middlewares.push(type_id);
60        }
61        self
62    }
63}
64
65macro_rules! route_methods {
66    ($($method:ident),+ $(,)?) => {
67        #[allow(non_snake_case)]
68        pub trait RouteMethods {
69            fn on<Arguments, Input, H: Handler<Arguments, Input>>(
70                self,
71                method: Method,
72                handler: H,
73            ) -> Route<H, Arguments, Input>;
74
75            $(
76                fn $method<Arguments, Input, H: Handler<Arguments, Input>>(
77                    self,
78                    handler: H,
79                ) -> Route<H, Arguments, Input>;
80            )+
81        }
82
83        #[allow(non_snake_case)]
84        impl RouteMethods for &'static str {
85            fn on<Arguments, Input, H: Handler<Arguments, Input>>(
86                self,
87                method: Method,
88                handler: H,
89            ) -> Route<H, Arguments, Input> {
90                Route {
91                    path: self,
92                    method,
93                    handler,
94                    operation_modifiers: Vec::new(),
95                    middlewares: Vec::new(),
96                    excluded_middlewares: Vec::new(),
97                    signature: PhantomData,
98                }
99            }
100
101            $(
102                fn $method<Arguments, Input, H: Handler<Arguments, Input>>(
103                    self,
104                    handler: H,
105                ) -> Route<H, Arguments, Input> {
106                    self.on(Method::$method, handler)
107                }
108            )+
109        }
110    };
111}
112
113route_methods!(GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, CONNECT, TRACE);
114
115#[doc(hidden)]
116pub trait Routes {
117    fn apply(self, router: &mut Router);
118}
119
120impl Routes for () {
121    fn apply(self, _router: &mut Router) {}
122}
123
124impl<Arguments: 'static, Input: 'static, H: Handler<Arguments, Input> + Send + Sync + 'static>
125    Routes for Route<H, Arguments, Input>
126{
127    fn apply(self, router: &mut Router) {
128        let mut operation = H::openapi();
129        for modifier in self.operation_modifiers {
130            modifier(&mut operation);
131        }
132        router.register(
133            self.method,
134            self.path,
135            self.handler,
136            operation,
137            self.middlewares,
138            self.excluded_middlewares,
139        );
140    }
141}
142
143impl Routes for Router {
144    fn apply(self, router: &mut Router) {
145        router.register_router(self);
146    }
147}
148
149macro_rules! impl_route_tuple {
150    ($($route:ident),+) => {
151        impl<$($route: Routes),+> Routes for ($($route,)+) {
152            #[allow(non_snake_case)]
153            fn apply(self, router: &mut Router) {
154                let ($($route,)+) = self;
155
156                $($route.apply(router);)+
157            }
158        }
159    };
160}
161
162serverkit_macros::impl_routes!(16);
163
164#[cfg(test)]
165mod tests {
166    use crate::{Config, Method, RouteMethods, Router};
167
168    async fn health() -> &'static str {
169        "ok"
170    }
171
172    async fn version() -> &'static str {
173        "0.1.0"
174    }
175
176    async fn accepted() -> &'static str {
177        "accepted"
178    }
179
180    #[test]
181    fn routes_can_be_registered_with_an_app() {
182        let _application = Router::new(
183            Config::new(),
184            (
185                "/health".GET(health),
186                "/version".GET(version),
187                "/post".POST(accepted),
188                "/put".PUT(accepted),
189                "/patch".PATCH(accepted),
190                "/delete".DELETE(accepted),
191                "/head".HEAD(accepted),
192                "/options".OPTIONS(accepted),
193                "/connect".CONNECT(accepted),
194                "/trace".TRACE(accepted),
195                "/propfind".on(Method::from_bytes(b"PROPFIND").unwrap(), accepted),
196            ),
197        );
198    }
199
200    #[test]
201    fn generates_route_tuples_through_the_configured_maximum() {
202        let _application = Router::new(
203            Config::new(),
204            (
205                "/1".GET(health),
206                "/2".GET(health),
207                "/3".GET(health),
208                "/4".GET(health),
209                "/5".GET(health),
210                "/6".GET(health),
211                "/7".GET(health),
212                "/8".GET(health),
213                "/9".GET(health),
214                "/10".GET(health),
215                "/11".GET(health),
216                "/12".GET(health),
217                "/13".GET(health),
218                "/14".GET(health),
219                "/15".GET(health),
220                "/16".GET(health),
221            ),
222        );
223    }
224}