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///     )
71///     .build()?;
72/// # Ok::<(), web_net::BuildError>(())
73/// ```
74///
75/// A closure that reads fields of a custom identity types its parameter,
76/// because the closure is checked before the builder fixes the type (a
77/// named `fn` handler needs nothing extra):
78///
79/// ```
80/// use web_net::{Cx, Route, get};
81///
82/// struct User { name: String }
83/// impl web_net::Identity for User {
84///     fn is_guest(&self) -> bool { self.name.is_empty() }
85/// }
86/// enum Rule { SignedIn }
87///
88/// let account: Route<User, Rule> = get("/account", |cx: &mut Cx<'_, User>| {
89///     let name = cx.identity().name.clone();
90///     cx.text(200, &name);
91///     Ok(())
92/// })
93/// .allow(Rule::SignedIn);
94/// # let _ = account;
95/// ```
96pub 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    /// A route at `pattern` with no methods yet.
107    #[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    /// Adds a handler for `method`. `HEAD` follows `GET` unless it has its
120    /// own; `OPTIONS` is answered for every route unless it has its own.
121    #[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    /// Adds a `GET` handler.
128    #[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    /// Adds a `POST` handler.
137    #[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    /// Adds a `PUT` handler.
146    #[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    /// Adds a `PATCH` handler.
155    #[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    /// Adds a `DELETE` handler.
164    #[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    /// Anyone may use the route, guests included. Public is a declaration,
173    /// never a default: a route with neither `public` nor `allow` stops the
174    /// build.
175    #[must_use]
176    pub fn public(mut self) -> Self {
177        self.access = Access::Public;
178        self
179    }
180
181    /// Those `rule` allows may use the route; the gate's `authorize` decides
182    /// on every request.
183    #[must_use]
184    pub fn allow(mut self, rule: R) -> Self {
185        self.access = Access::Rule(Arc::new(rule));
186        self
187    }
188
189    /// A name for logs, events, and hooks.
190    #[must_use]
191    pub fn name(mut self, name: &'static str) -> Self {
192        self.name = Some(name);
193        self
194    }
195
196    /// How the handlers behave: `Quick` (the default), `Io`, or `Cpu`. A
197    /// dispatcher in front of web-net reads it through
198    /// [`App::classify`](crate::App::classify) to move slow work off the
199    /// connection's worker.
200    #[must_use]
201    pub fn work(mut self, work: WorkClass) -> Self {
202        self.work = work;
203        self
204    }
205
206    /// The template [`Cx::render`](crate::Cx::render) uses. Resolved when the
207    /// application is built: an unknown name stops the build.
208    #[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    /// A route with a `GET` handler (and so `HEAD`).
243    get, Get
244);
245method_fn!(
246    /// A route with a `POST` handler.
247    post, Post
248);
249method_fn!(
250    /// A route with a `PUT` handler.
251    put, Put
252);
253method_fn!(
254    /// A route with a `PATCH` handler.
255    patch, Patch
256);
257method_fn!(
258    /// A route with a `DELETE` handler.
259    delete, Delete
260);
261
262/// Routes sharing a path prefix and a default access.
263///
264/// A route that declares its own access keeps it; one that declares none
265/// takes the group's.
266///
267/// ```
268/// use web_net::{App, Group, get};
269///
270/// let app = App::builder()
271///     .group(
272///         Group::new("/docs")
273///             .public()
274///             .route(get("/", |cx| { cx.text(200, "index"); Ok(()) }))
275///             .route(get("/:page", |cx| { cx.text(200, "page"); Ok(()) })),
276///     )
277///     .build()?;
278/// # Ok::<(), web_net::BuildError>(())
279/// ```
280pub 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    /// A group under `prefix` (`/admin`).
288    #[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    /// Routes without their own access are public.
298    #[must_use]
299    pub fn public(mut self) -> Self {
300        self.access = Access::Public;
301        self
302    }
303
304    /// Routes without their own access need `rule`.
305    #[must_use]
306    pub fn allow(mut self, rule: R) -> Self {
307        self.access = Access::Rule(Arc::new(rule));
308        self
309    }
310
311    /// Adds a route; its pattern is joined to the prefix.
312    #[must_use]
313    pub fn route(mut self, route: Route<I, R>) -> Self {
314        self.routes.push(route);
315        self
316    }
317
318    /// Adds a nested group; its prefix is joined to this one, and it takes
319    /// this group's access when it declares none.
320    #[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
358/// `/admin` + `/` = `/admin`; `/admin` + `/users` = `/admin/users`.
359pub(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}