Skip to main content

web_net/
route.rs

1//! Route declarations: a path, its methods and handlers, and its access.
2
3use std::sync::Arc;
4
5use serve_net::{Method, WorkClass};
6
7use crate::{Cx, Result};
8
9/// A request handler. Any `Fn(&mut Cx<'_, I>) -> web_net::Result` is one.
10pub trait Handler<I>: Send + Sync + 'static {
11    /// Handles one cleared request.
12    ///
13    /// # Errors
14    ///
15    /// An [`Error`](crate::Error), answered with the site's error page for
16    /// its status.
17    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/// Who may use a route.
31#[derive(Debug)]
32pub(crate) enum Access<R> {
33    /// Not declared: the build refuses the route.
34    Undeclared,
35    /// Anyone, guests included; the gate's `authorize` is not called.
36    Public,
37    /// Those the rule allows.
38    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
53/// A route: a path pattern, one handler per method, and who may use it.
54///
55/// Patterns are made of literal segments, parameters (`:id`, one segment)
56/// and one wildcard at the end (`*rest`, the rest of the path). Literals are
57/// written decoded (`/café`); web-net compares them with the canonical
58/// request path.
59///
60/// ```
61/// use web_net::{App, get, Route};
62///
63/// let app = App::builder()
64///     .route(get("/", |cx| { cx.html(200, "<h1>Home</h1>"); Ok(()) }).public())
65///     .route(
66///         Route::at("/contact")
67///             .get(|cx| { cx.html(200, "<form method=post></form>"); Ok(()) })
68///             .post(|cx| { cx.see_other("/contact/thanks") })
69///             .public()
70///             // A real form gets CSRF protection: Builder::csrf and a gate
71///             // whose identities carry a binding.
72///             .csrf_exempt("example: the POST changes nothing"),
73///     )
74///     .build()?;
75/// # Ok::<(), web_net::BuildError>(())
76/// ```
77///
78/// A closure that reads fields of a custom identity types its parameter,
79/// because the closure is checked before the builder fixes the type (a
80/// named `fn` handler needs nothing extra):
81///
82/// ```
83/// use web_net::{Cx, Route, get};
84///
85/// struct User { name: String }
86/// impl web_net::Identity for User {
87///     fn is_guest(&self) -> bool { self.name.is_empty() }
88/// }
89/// enum Rule { SignedIn }
90///
91/// let account: Route<User, Rule> = get("/account", |cx: &mut Cx<'_, User>| {
92///     let name = cx.identity().name.clone();
93///     cx.text(200, &name);
94///     Ok(())
95/// })
96/// .allow(Rule::SignedIn);
97/// # let _ = account;
98/// ```
99pub 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    /// A route at `pattern` with no methods yet.
112    #[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    /// Adds a handler for `method`. `HEAD` follows `GET` unless it has its
127    /// own; `OPTIONS` is answered for every route unless it has its own.
128    #[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    /// Adds a `GET` handler.
135    #[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    /// Adds a `POST` handler.
144    #[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    /// Adds a `PUT` handler.
153    #[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    /// Adds a `PATCH` handler.
162    #[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    /// Adds a `DELETE` handler.
171    #[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    /// Anyone may use the route, guests included. Public is a declaration,
180    /// never a default: a route with neither `public` nor `allow` stops the
181    /// build.
182    #[must_use]
183    pub fn public(mut self) -> Self {
184        self.access = Access::Public;
185        self
186    }
187
188    /// Those `rule` allows may use the route; the gate's `authorize` decides
189    /// on every request.
190    #[must_use]
191    pub fn allow(mut self, rule: R) -> Self {
192        self.access = Access::Rule(Arc::new(rule));
193        self
194    }
195
196    /// A name for logs, events, and hooks.
197    #[must_use]
198    pub fn name(mut self, name: &'static str) -> Self {
199        self.name = Some(name);
200        self
201    }
202
203    /// How the handlers behave: `Quick` (the default), `Io`, or `Cpu`. A
204    /// dispatcher in front of web-net reads it through
205    /// [`App::classify`](crate::App::classify) to move slow work off the
206    /// connection's worker.
207    #[must_use]
208    pub fn work(mut self, work: WorkClass) -> Self {
209        self.work = work;
210        self
211    }
212
213    /// Takes the route out of CSRF protection, with the reason (a webhook
214    /// signed by its sender, an API authenticated by a bearer token that a
215    /// browser never sends on its own). The reason is required and must not
216    /// be empty: an exemption is a decision, never a default.
217    #[must_use]
218    pub fn csrf_exempt(mut self, reason: &'static str) -> Self {
219        self.csrf_exempt = Some(reason);
220        self
221    }
222
223    /// The budgets for reading this route's forms (field count, sizes, files;
224    /// default `form_net::Limits::DEFAULT`). Keep them to what the form
225    /// needs: they bound what one request can make the server hold.
226    #[must_use]
227    pub fn form_limits(mut self, limits: form_net::Limits) -> Self {
228        self.form_limits = limits;
229        self
230    }
231
232    /// The template [`Cx::render`](crate::Cx::render) uses. Resolved when the
233    /// application is built: an unknown name stops the build.
234    #[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    /// A route with a `GET` handler (and so `HEAD`).
269    get, Get
270);
271method_fn!(
272    /// A route with a `POST` handler.
273    post, Post
274);
275method_fn!(
276    /// A route with a `PUT` handler.
277    put, Put
278);
279method_fn!(
280    /// A route with a `PATCH` handler.
281    patch, Patch
282);
283method_fn!(
284    /// A route with a `DELETE` handler.
285    delete, Delete
286);
287
288/// Routes sharing a path prefix and a default access.
289///
290/// A route that declares its own access keeps it; one that declares none
291/// takes the group's.
292///
293/// ```
294/// use web_net::{App, Group, get};
295///
296/// let app = App::builder()
297///     .group(
298///         Group::new("/docs")
299///             .public()
300///             .route(get("/", |cx| { cx.text(200, "index"); Ok(()) }))
301///             .route(get("/:page", |cx| { cx.text(200, "page"); Ok(()) })),
302///     )
303///     .build()?;
304/// # Ok::<(), web_net::BuildError>(())
305/// ```
306pub 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    /// A group under `prefix` (`/admin`).
314    #[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    /// Routes without their own access are public.
324    #[must_use]
325    pub fn public(mut self) -> Self {
326        self.access = Access::Public;
327        self
328    }
329
330    /// Routes without their own access need `rule`.
331    #[must_use]
332    pub fn allow(mut self, rule: R) -> Self {
333        self.access = Access::Rule(Arc::new(rule));
334        self
335    }
336
337    /// Adds a route; its pattern is joined to the prefix.
338    #[must_use]
339    pub fn route(mut self, route: Route<I, R>) -> Self {
340        self.routes.push(route);
341        self
342    }
343
344    /// Adds a nested group; its prefix is joined to this one, and it takes
345    /// this group's access when it declares none.
346    #[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
384/// `/admin` + `/` = `/admin`; `/admin` + `/users` = `/admin/users`.
385pub(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}