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}