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> {
100 pub(crate) pattern: String,
101 pub(crate) handlers: Vec<(Method, BoxHandler<I>)>,
102 pub(crate) access: Access<R>,
103 pub(crate) name: Option<&'static str>,
104 pub(crate) work: WorkClass,
105 pub(crate) template: Option<&'static str>,
106 pub(crate) csrf_exempt: Option<&'static str>,
107 pub(crate) form_limits: form_net::Limits,
108}
109
110impl<I: 'static, R> Route<I, R> {
111 #[must_use]
113 pub fn at(pattern: impl Into<String>) -> Self {
114 Self {
115 pattern: pattern.into(),
116 handlers: Vec::new(),
117 access: Access::Undeclared,
118 name: None,
119 work: WorkClass::Quick,
120 template: None,
121 csrf_exempt: None,
122 form_limits: form_net::Limits::DEFAULT,
123 }
124 }
125
126 #[must_use]
129 pub fn on(mut self, method: Method, handler: impl Handler<I>) -> Self {
130 self.handlers.push((method, Arc::new(handler)));
131 self
132 }
133
134 #[must_use]
136 pub fn get<F>(self, f: F) -> Self
137 where
138 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
139 {
140 self.on(Method::Get, f)
141 }
142
143 #[must_use]
145 pub fn post<F>(self, f: F) -> Self
146 where
147 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
148 {
149 self.on(Method::Post, f)
150 }
151
152 #[must_use]
154 pub fn put<F>(self, f: F) -> Self
155 where
156 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
157 {
158 self.on(Method::Put, f)
159 }
160
161 #[must_use]
163 pub fn patch<F>(self, f: F) -> Self
164 where
165 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
166 {
167 self.on(Method::Patch, f)
168 }
169
170 #[must_use]
172 pub fn delete<F>(self, f: F) -> Self
173 where
174 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
175 {
176 self.on(Method::Delete, f)
177 }
178
179 #[must_use]
183 pub fn public(mut self) -> Self {
184 self.access = Access::Public;
185 self
186 }
187
188 #[must_use]
191 pub fn allow(mut self, rule: R) -> Self {
192 self.access = Access::Rule(Arc::new(rule));
193 self
194 }
195
196 #[must_use]
198 pub fn name(mut self, name: &'static str) -> Self {
199 self.name = Some(name);
200 self
201 }
202
203 #[must_use]
208 pub fn work(mut self, work: WorkClass) -> Self {
209 self.work = work;
210 self
211 }
212
213 #[must_use]
218 pub fn csrf_exempt(mut self, reason: &'static str) -> Self {
219 self.csrf_exempt = Some(reason);
220 self
221 }
222
223 #[must_use]
227 pub fn form_limits(mut self, limits: form_net::Limits) -> Self {
228 self.form_limits = limits;
229 self
230 }
231
232 #[must_use]
235 pub fn template(mut self, name: &'static str) -> Self {
236 self.template = Some(name);
237 self
238 }
239}
240
241impl<I, R> std::fmt::Debug for Route<I, R> {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 f.debug_struct("Route")
244 .field("pattern", &self.pattern)
245 .field(
246 "methods",
247 &self.handlers.iter().map(|(m, _)| *m).collect::<Vec<_>>(),
248 )
249 .field("name", &self.name)
250 .finish_non_exhaustive()
251 }
252}
253
254macro_rules! method_fn {
255 ($(#[$doc:meta])* $fn:ident, $m:ident) => {
256 $(#[$doc])*
257 #[must_use]
258 pub fn $fn<I: 'static, R, F>(pattern: impl Into<String>, f: F) -> Route<I, R>
259 where
260 F: Fn(&mut Cx<'_, I>) -> Result + Send + Sync + 'static,
261 {
262 Route::at(pattern).on(Method::$m, f)
263 }
264 };
265}
266
267method_fn!(
268 get, Get
270);
271method_fn!(
272 post, Post
274);
275method_fn!(
276 put, Put
278);
279method_fn!(
280 patch, Patch
282);
283method_fn!(
284 delete, Delete
286);
287
288pub struct Group<I, R> {
307 pub(crate) prefix: String,
308 pub(crate) access: Access<R>,
309 pub(crate) routes: Vec<Route<I, R>>,
310}
311
312impl<I: 'static, R> Group<I, R> {
313 #[must_use]
315 pub fn new(prefix: impl Into<String>) -> Self {
316 Self {
317 prefix: prefix.into(),
318 access: Access::Undeclared,
319 routes: Vec::new(),
320 }
321 }
322
323 #[must_use]
325 pub fn public(mut self) -> Self {
326 self.access = Access::Public;
327 self
328 }
329
330 #[must_use]
332 pub fn allow(mut self, rule: R) -> Self {
333 self.access = Access::Rule(Arc::new(rule));
334 self
335 }
336
337 #[must_use]
339 pub fn route(mut self, route: Route<I, R>) -> Self {
340 self.routes.push(route);
341 self
342 }
343
344 #[must_use]
347 pub fn group(mut self, group: Group<I, R>) -> Self {
348 let inherited = match &group.access {
349 Access::Undeclared => self.access.clone(),
350 a => a.clone(),
351 };
352 for mut r in group.routes {
353 r.pattern = join(&group.prefix, &r.pattern);
354 if matches!(r.access, Access::Undeclared) {
355 r.access = inherited.clone();
356 }
357 self.routes.push(r);
358 }
359 self
360 }
361
362 pub(crate) fn into_routes(self) -> impl Iterator<Item = Route<I, R>> {
363 let prefix = self.prefix;
364 let access = self.access;
365 self.routes.into_iter().map(move |mut r| {
366 r.pattern = join(&prefix, &r.pattern);
367 if matches!(r.access, Access::Undeclared) {
368 r.access = access.clone();
369 }
370 r
371 })
372 }
373}
374
375impl<I, R> std::fmt::Debug for Group<I, R> {
376 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377 f.debug_struct("Group")
378 .field("prefix", &self.prefix)
379 .field("routes", &self.routes)
380 .finish_non_exhaustive()
381 }
382}
383
384pub(crate) fn join(prefix: &str, pattern: &str) -> String {
386 let p = prefix.trim_end_matches('/');
387 if pattern == "/" || pattern.is_empty() {
388 if p.is_empty() { "/".into() } else { p.into() }
389 } else if pattern.starts_with('/') {
390 format!("{p}{pattern}")
391 } else {
392 format!("{p}/{pattern}")
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::join;
399
400 #[test]
401 fn test_join() {
402 assert_eq!(join("/admin", "/"), "/admin");
403 assert_eq!(join("/admin/", "/users"), "/admin/users");
404 assert_eq!(join("/", "/x"), "/x");
405 assert_eq!(join("", "/"), "/");
406 assert_eq!(join("/a", "b"), "/a/b");
407 }
408}