1use std::sync::Arc;
4
5use serve_net::{Method, WorkClass};
6
7use crate::{Cx, Result};
8
9pub trait Handler<I>: Send + Sync + 'static {
11 fn call(&self, cx: &mut Cx<'_, I>) -> Result;
18}
19
20impl<I, F> Handler<I> for F
21where
22 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
23{
24 #[inline]
25 fn call(&self, cx: &mut Cx<'_, I>) -> Result {
26 self(cx)
27 }
28}
29
30#[derive(Debug)]
32pub(crate) enum Access<R> {
33 Undeclared,
35 Public,
37 Rule(Arc<R>),
39}
40
41impl<R> Clone for Access<R> {
42 fn clone(&self) -> Self {
43 match self {
44 Self::Undeclared => Self::Undeclared,
45 Self::Public => Self::Public,
46 Self::Rule(r) => Self::Rule(Arc::clone(r)),
47 }
48 }
49}
50
51pub(crate) type BoxHandler<I> = Arc<dyn Handler<I>>;
52
53pub struct Route<I, R> {
97 pub(crate) pattern: String,
98 pub(crate) handlers: Vec<(Method, BoxHandler<I>)>,
99 pub(crate) access: Access<R>,
100 pub(crate) name: Option<&'static str>,
101 pub(crate) work: WorkClass,
102 pub(crate) template: Option<&'static str>,
103}
104
105impl<I: 'static, R> Route<I, R> {
106 #[must_use]
108 pub fn at(pattern: impl Into<String>) -> Self {
109 Self {
110 pattern: pattern.into(),
111 handlers: Vec::new(),
112 access: Access::Undeclared,
113 name: None,
114 work: WorkClass::Quick,
115 template: None,
116 }
117 }
118
119 #[must_use]
122 pub fn on(mut self, method: Method, handler: impl Handler<I>) -> Self {
123 self.handlers.push((method, Arc::new(handler)));
124 self
125 }
126
127 #[must_use]
129 pub fn get<F>(self, f: F) -> Self
130 where
131 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
132 {
133 self.on(Method::Get, f)
134 }
135
136 #[must_use]
138 pub fn post<F>(self, f: F) -> Self
139 where
140 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
141 {
142 self.on(Method::Post, f)
143 }
144
145 #[must_use]
147 pub fn put<F>(self, f: F) -> Self
148 where
149 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
150 {
151 self.on(Method::Put, f)
152 }
153
154 #[must_use]
156 pub fn patch<F>(self, f: F) -> Self
157 where
158 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
159 {
160 self.on(Method::Patch, f)
161 }
162
163 #[must_use]
165 pub fn delete<F>(self, f: F) -> Self
166 where
167 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
168 {
169 self.on(Method::Delete, f)
170 }
171
172 #[must_use]
176 pub fn public(mut self) -> Self {
177 self.access = Access::Public;
178 self
179 }
180
181 #[must_use]
184 pub fn allow(mut self, rule: R) -> Self {
185 self.access = Access::Rule(Arc::new(rule));
186 self
187 }
188
189 #[must_use]
191 pub fn name(mut self, name: &'static str) -> Self {
192 self.name = Some(name);
193 self
194 }
195
196 #[must_use]
201 pub fn work(mut self, work: WorkClass) -> Self {
202 self.work = work;
203 self
204 }
205
206 #[must_use]
209 pub fn template(mut self, name: &'static str) -> Self {
210 self.template = Some(name);
211 self
212 }
213}
214
215impl<I, R> std::fmt::Debug for Route<I, R> {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 f.debug_struct("Route")
218 .field("pattern", &self.pattern)
219 .field(
220 "methods",
221 &self.handlers.iter().map(|(m, _)| *m).collect::<Vec<_>>(),
222 )
223 .field("name", &self.name)
224 .finish_non_exhaustive()
225 }
226}
227
228macro_rules! method_fn {
229 ($(#[$doc:meta])* $fn:ident, $m:ident) => {
230 $(#[$doc])*
231 #[must_use]
232 pub fn $fn<I: 'static, R, F>(pattern: impl Into<String>, f: F) -> Route<I, R>
233 where
234 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
235 {
236 Route::at(pattern).on(Method::$m, f)
237 }
238 };
239}
240
241method_fn!(
242 get, Get
244);
245method_fn!(
246 post, Post
248);
249method_fn!(
250 put, Put
252);
253method_fn!(
254 patch, Patch
256);
257method_fn!(
258 delete, Delete
260);
261
262pub struct Group<I, R> {
281 pub(crate) prefix: String,
282 pub(crate) access: Access<R>,
283 pub(crate) routes: Vec<Route<I, R>>,
284}
285
286impl<I: 'static, R> Group<I, R> {
287 #[must_use]
289 pub fn new(prefix: impl Into<String>) -> Self {
290 Self {
291 prefix: prefix.into(),
292 access: Access::Undeclared,
293 routes: Vec::new(),
294 }
295 }
296
297 #[must_use]
299 pub fn public(mut self) -> Self {
300 self.access = Access::Public;
301 self
302 }
303
304 #[must_use]
306 pub fn allow(mut self, rule: R) -> Self {
307 self.access = Access::Rule(Arc::new(rule));
308 self
309 }
310
311 #[must_use]
313 pub fn route(mut self, route: Route<I, R>) -> Self {
314 self.routes.push(route);
315 self
316 }
317
318 #[must_use]
321 pub fn group(mut self, group: Group<I, R>) -> Self {
322 let inherited = match &group.access {
323 Access::Undeclared => self.access.clone(),
324 a => a.clone(),
325 };
326 for mut r in group.routes {
327 r.pattern = join(&group.prefix, &r.pattern);
328 if matches!(r.access, Access::Undeclared) {
329 r.access = inherited.clone();
330 }
331 self.routes.push(r);
332 }
333 self
334 }
335
336 pub(crate) fn into_routes(self) -> impl Iterator<Item = Route<I, R>> {
337 let prefix = self.prefix;
338 let access = self.access;
339 self.routes.into_iter().map(move |mut r| {
340 r.pattern = join(&prefix, &r.pattern);
341 if matches!(r.access, Access::Undeclared) {
342 r.access = access.clone();
343 }
344 r
345 })
346 }
347}
348
349impl<I, R> std::fmt::Debug for Group<I, R> {
350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351 f.debug_struct("Group")
352 .field("prefix", &self.prefix)
353 .field("routes", &self.routes)
354 .finish_non_exhaustive()
355 }
356}
357
358pub(crate) fn join(prefix: &str, pattern: &str) -> String {
360 let p = prefix.trim_end_matches('/');
361 if pattern == "/" || pattern.is_empty() {
362 if p.is_empty() { "/".into() } else { p.into() }
363 } else if pattern.starts_with('/') {
364 format!("{p}{pattern}")
365 } else {
366 format!("{p}/{pattern}")
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use super::join;
373
374 #[test]
375 fn test_join() {
376 assert_eq!(join("/admin", "/"), "/admin");
377 assert_eq!(join("/admin/", "/users"), "/admin/users");
378 assert_eq!(join("/", "/x"), "/x");
379 assert_eq!(join("", "/"), "/");
380 assert_eq!(join("/a", "b"), "/a/b");
381 }
382}