Skip to main content

rws_server/group/
mod.rs

1//! Route groups, guards and resources (18.35.0, #94).
2//!
3//! A group registers routes under a shared path prefix and runs the same
4//! guards — and, on [`AppWithState`](crate::state::AppWithState), the same
5//! middleware — in front of each:
6//!
7//! ```rust
8//! # extern crate rws_server as rust_web_server;
9//! use rust_web_server::app::App;
10//! use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
11//!
12//! fn signed_in(request: &rust_web_server::request::Request, _: &rust_web_server::server::ConnectionInfo, _: &()) -> Result<(), Response> {
13//!     match request.header("authorization") {
14//!         Some(_) => Ok(()),
15//!         None => Err(Response::text(STATUS_CODE_REASON_PHRASE.n401_unauthorized, "sign in")),
16//!     }
17//! }
18//!
19//! let app = App::with_state(()).group("/admin", |admin| {
20//!     admin
21//!         .guard(signed_in)
22//!         .get("/", |_, _, _, _| Response::text(STATUS_CODE_REASON_PHRASE.n200_ok, "dashboard"))
23//!         .resource("/posts", |posts| {
24//!             posts
25//!                 .index(|_, _, _, _| Response::text(STATUS_CODE_REASON_PHRASE.n200_ok, "all posts"))
26//!                 .show(|_, params, _, _| Response::text(STATUS_CODE_REASON_PHRASE.n200_ok, params.get("id").unwrap_or("")))
27//!         })
28//! });
29//! ```
30//!
31//! Guards (`before_action`) are checked in the order added, outer groups
32//! first, and apply to every route of the group whichever order they were
33//! added in; the first `Err(response)` answers the request. Groups nest.
34//!
35//! [`Resource`] registers the conventional routes for a collection:
36//!
37//! | Method | Path | Builder | Name |
38//! |---|---|---|---|
39//! | GET | `/posts` | `index` | `posts.index` |
40//! | GET | `/posts/new` | `new_form` | `posts.new` |
41//! | POST | `/posts` | `create` | `posts.create` |
42//! | GET | `/posts/{id}` | `show` | `posts.show` |
43//! | GET | `/posts/{id}/edit` | `edit` | `posts.edit` |
44//! | PUT, PATCH | `/posts/{id}` | `update` | `posts.update` |
45//! | DELETE | `/posts/{id}` | `destroy` | `posts.destroy` |
46//!
47//! `new` comes before `{id}`, so `/posts/new` is never taken as an id. The
48//! names (the resource path's last segment, then the action) work with
49//! [`url_for`](crate::router::url_for); `.name(…)` on the resource changes
50//! the prefix.
51
52#[cfg(test)]
53mod tests;
54
55use std::sync::Arc;
56
57use crate::application::Application;
58use crate::middleware::Middleware;
59use crate::request::Request;
60use crate::response::Response;
61use crate::router::PathParams;
62use crate::server::ConnectionInfo;
63
64/// A route guard: `Ok(())` lets the request through, `Err(response)`
65/// answers it (a redirect to the login page, a `401`, a `403`).
66pub type Guard<S> = Arc<dyn Fn(&Request, &ConnectionInfo, &S) -> Result<(), Response> + Send + Sync>;
67
68/// A sync handler as groups store it.
69pub(crate) type SyncHandler<S> = Arc<dyn Fn(&Request, &PathParams, &ConnectionInfo, &Arc<S>) -> Response + Send + Sync>;
70
71/// A route a group collected, before the app registers it.
72pub(crate) struct GroupRoute<H> {
73    pub(crate) method: &'static str,
74    pub(crate) pattern: String,
75    pub(crate) name: Option<String>,
76    pub(crate) handler: H,
77}
78
79/// Join a group prefix and a route pattern: `/admin` + `/` = `/admin`,
80/// `/admin` + `/posts` = `/admin/posts`, `` + `` = `/`.
81pub fn join_path(prefix: &str, pattern: &str) -> String {
82    let prefix = prefix.trim_end_matches('/');
83    let pattern = pattern.trim_start_matches('/');
84    let joined = match (prefix.is_empty(), pattern.is_empty()) {
85        (_, true) => prefix.to_string(),
86        (true, false) => format!("/{pattern}"),
87        (false, false) => format!("{prefix}/{pattern}"),
88    };
89    if joined.is_empty() {
90        "/".to_string()
91    } else if joined.starts_with('/') {
92        joined
93    } else {
94        format!("/{joined}")
95    }
96}
97
98/// The name prefix of a resource at `path`: its last literal segment.
99fn resource_name(path: &str) -> String {
100    path.rsplit('/').find(|s| !s.is_empty() && !s.starts_with([':', '*', '{'])).unwrap_or("resource").to_string()
101}
102
103/// The terminal of a group route's middleware chain: the handler itself.
104struct Endpoint<'a, S> {
105    handler: &'a SyncHandler<S>,
106    params: &'a PathParams,
107    state: &'a Arc<S>,
108}
109
110impl<S> Application for Endpoint<'_, S> {
111    fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
112        Ok((self.handler)(request, self.params, connection, self.state))
113    }
114}
115
116/// The rest of the middleware chain after `layers[0]`.
117struct Rest<'a> {
118    layers: &'a [Arc<dyn Middleware>],
119    endpoint: &'a dyn Application,
120}
121
122impl Application for Rest<'_> {
123    fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
124        match self.layers.split_first() {
125            Some((layer, layers)) => layer.handle(request, connection, &Rest { layers, endpoint: self.endpoint }),
126            None => self.endpoint.execute(request, connection),
127        }
128    }
129}
130
131fn internal_error(message: String) -> Response {
132    use crate::error::{AppError, IntoResponse};
133    AppError::Internal(message).into_response()
134}
135
136/// Wrap `handler` in `guards` then `layers` (first = outermost).
137fn wrap_sync<S: Send + Sync + 'static>(handler: SyncHandler<S>, guards: Arc<[Guard<S>]>, layers: Arc<[Arc<dyn Middleware>]>) -> SyncHandler<S> {
138    if guards.is_empty() && layers.is_empty() {
139        return handler;
140    }
141    Arc::new(move |request, params, connection, state| {
142        for guard in guards.iter() {
143            if let Err(response) = guard(request, connection, state) {
144                return response;
145            }
146        }
147        if layers.is_empty() {
148            return handler(request, params, connection, state);
149        }
150        let endpoint = Endpoint { handler: &handler, params, state };
151        match (Rest { layers: &layers, endpoint: &endpoint }).execute(request, connection) {
152            Ok(response) => response,
153            Err(message) => internal_error(message),
154        }
155    })
156}
157
158// ── Sync groups ──────────────────────────────────────────────────────────────
159
160/// Routes under one path prefix for [`AppWithState`](crate::state::AppWithState),
161/// sharing guards and middleware. Made by `app.group(prefix, |g| …)`.
162pub struct Group<S> {
163    prefix: String,
164    guards: Vec<Guard<S>>,
165    layers: Vec<Arc<dyn Middleware>>,
166    routes: Vec<GroupRoute<SyncHandler<S>>>,
167}
168
169impl<S: Send + Sync + 'static> Group<S> {
170    pub(crate) fn new(prefix: &str) -> Self {
171        Group { prefix: prefix.to_string(), guards: Vec::new(), layers: Vec::new(), routes: Vec::new() }
172    }
173
174    fn push(mut self, method: &'static str, pattern: &str, handler: SyncHandler<S>) -> Self {
175        self.routes.push(GroupRoute { method, pattern: join_path(&self.prefix, pattern), name: None, handler });
176        self
177    }
178
179    /// Run `guard` before every route of this group (and its nested groups).
180    pub fn guard<G>(mut self, guard: G) -> Self
181    where
182        G: Fn(&Request, &ConnectionInfo, &S) -> Result<(), Response> + Send + Sync + 'static,
183    {
184        self.guards.push(Arc::new(guard));
185        self
186    }
187
188    /// Run `layer` around every route of this group, inside its guards;
189    /// the first layer added is the outermost.
190    pub fn wrap<M: Middleware + 'static>(mut self, layer: M) -> Self {
191        self.layers.push(Arc::new(layer));
192        self
193    }
194
195    /// Name the route registered last, for [`url_for`](crate::router::url_for).
196    pub fn name(mut self, name: &str) -> Self {
197        if let Some(route) = self.routes.last_mut() {
198            route.name = Some(name.to_string());
199        }
200        self
201    }
202
203    /// Register a `GET` handler at `prefix + pattern`.
204    pub fn get<F>(self, pattern: &str, handler: F) -> Self
205    where
206        F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
207    {
208        self.push("GET", pattern, Arc::new(move |r, p, c, s| handler(r, p, c, s)))
209    }
210
211    /// Register a `POST` handler at `prefix + pattern`.
212    pub fn post<F>(self, pattern: &str, handler: F) -> Self
213    where
214        F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
215    {
216        self.push("POST", pattern, Arc::new(move |r, p, c, s| handler(r, p, c, s)))
217    }
218
219    /// Register a `PUT` handler at `prefix + pattern`.
220    pub fn put<F>(self, pattern: &str, handler: F) -> Self
221    where
222        F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
223    {
224        self.push("PUT", pattern, Arc::new(move |r, p, c, s| handler(r, p, c, s)))
225    }
226
227    /// Register a `PATCH` handler at `prefix + pattern`.
228    pub fn patch<F>(self, pattern: &str, handler: F) -> Self
229    where
230        F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
231    {
232        self.push("PATCH", pattern, Arc::new(move |r, p, c, s| handler(r, p, c, s)))
233    }
234
235    /// Register a `DELETE` handler at `prefix + pattern`.
236    pub fn delete<F>(self, pattern: &str, handler: F) -> Self
237    where
238        F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
239    {
240        self.push("DELETE", pattern, Arc::new(move |r, p, c, s| handler(r, p, c, s)))
241    }
242
243    /// Register a typed handler (`#[get("/path")]` & co.) at
244    /// `prefix + its pattern`.
245    pub fn route<H: crate::handler::Handler<S>>(self, handler: H) -> Self {
246        self.push(H::METHOD, H::PATTERN, Arc::new(move |r, p, c, s| handler.handle(r, p, c, s)))
247    }
248
249    /// A nested group at `prefix + pattern`: its routes run this group's
250    /// guards and middleware first, then its own.
251    pub fn group(mut self, pattern: &str, build: impl FnOnce(Group<S>) -> Group<S>) -> Self {
252        let inner = build(Group::new(&join_path(&self.prefix, pattern)));
253        self.routes.extend(inner.finish());
254        self
255    }
256
257    /// The conventional routes of a resource at `prefix + path` (see the
258    /// module docs).
259    pub fn resource(mut self, path: &str, build: impl FnOnce(Resource<S>) -> Resource<S>) -> Self {
260        let resource = build(Resource::new(&join_path(&self.prefix, path)));
261        self.routes.extend(resource.finish());
262        self
263    }
264
265    /// The routes with this group's guards and middleware applied.
266    pub(crate) fn finish(self) -> Vec<GroupRoute<SyncHandler<S>>> {
267        let guards: Arc<[Guard<S>]> = self.guards.into();
268        let layers: Arc<[Arc<dyn Middleware>]> = self.layers.into();
269        self.routes
270            .into_iter()
271            .map(|route| GroupRoute { handler: wrap_sync(route.handler, Arc::clone(&guards), Arc::clone(&layers)), ..route })
272            .collect()
273    }
274}
275
276/// The conventional routes of a collection (see the module docs). Made by
277/// `app.resource(path, |r| …)` or a group's `resource`.
278pub struct Resource<S> {
279    group: Group<S>,
280    name: String,
281    actions: Vec<(&'static str, &'static str, &'static str, SyncHandler<S>)>,
282}
283
284macro_rules! resource_action {
285    ($(#[$doc:meta])* $fn:ident, $action:literal, $method:literal, $suffix:literal) => {
286        $(#[$doc])*
287        pub fn $fn<F>(mut self, handler: F) -> Self
288        where
289            F: Fn(&Request, &PathParams, &ConnectionInfo, &S) -> Response + Send + Sync + 'static,
290        {
291            self.actions.push(($action, $method, $suffix, Arc::new(move |r, p, c, s| handler(r, p, c, s))));
292            self
293        }
294    };
295}
296
297impl<S: Send + Sync + 'static> Resource<S> {
298    fn new(path: &str) -> Self {
299        Resource { name: resource_name(path), group: Group::new(path), actions: Vec::new() }
300    }
301
302    /// The route names' prefix (default: the path's last segment).
303    pub fn name(mut self, name: &str) -> Self {
304        self.name = name.to_string();
305        self
306    }
307
308    /// Run `guard` before every route of this resource.
309    pub fn guard<G>(mut self, guard: G) -> Self
310    where
311        G: Fn(&Request, &ConnectionInfo, &S) -> Result<(), Response> + Send + Sync + 'static,
312    {
313        self.group = self.group.guard(guard);
314        self
315    }
316
317    /// Run `layer` around every route of this resource.
318    pub fn wrap<M: Middleware + 'static>(mut self, layer: M) -> Self {
319        self.group = self.group.wrap(layer);
320        self
321    }
322
323    resource_action!(/// `GET /posts`: the list.
324        index, "index", "GET", "");
325    resource_action!(/// `GET /posts/new`: the form for a new one.
326        new_form, "new", "GET", "/new");
327    resource_action!(/// `POST /posts`: create one.
328        create, "create", "POST", "");
329    resource_action!(/// `GET /posts/{id}`: one.
330        show, "show", "GET", "/{id}");
331    resource_action!(/// `GET /posts/{id}/edit`: the form to change one.
332        edit, "edit", "GET", "/{id}/edit");
333    resource_action!(/// `PUT` and `PATCH /posts/{id}`: change one.
334        update, "update", "PUT", "/{id}");
335    resource_action!(/// `DELETE /posts/{id}`: delete one.
336        destroy, "destroy", "DELETE", "/{id}");
337
338    /// More routes under the resource's path (`/posts/{id}/publish`), with
339    /// its guards.
340    pub fn extra(mut self, build: impl FnOnce(Group<S>) -> Group<S>) -> Self {
341        let prefix = self.group.prefix.clone();
342        let inner = build(Group::new(&prefix));
343        self.group.routes.extend(inner.finish());
344        self
345    }
346
347    fn finish(mut self) -> Vec<GroupRoute<SyncHandler<S>>> {
348        // `new` before `{id}`, whatever order the builder calls came in.
349        let order = ["index", "new", "create", "show", "edit", "update", "destroy"];
350        self.actions.sort_by_key(|(action, ..)| order.iter().position(|a| a == action));
351        let mut group = self.group;
352        let mut extra = std::mem::take(&mut group.routes);
353        for (action, method, suffix, handler) in self.actions {
354            let pattern = join_path(&group.prefix, suffix);
355            let name = Some(format!("{}.{}", self.name, action));
356            if action == "update" {
357                group.routes.push(GroupRoute { method: "PATCH", pattern: pattern.clone(), name: None, handler: Arc::clone(&handler) });
358            }
359            group.routes.push(GroupRoute { method, pattern, name, handler });
360        }
361        group.routes.append(&mut extra);
362        group.finish()
363    }
364}
365
366// ── Async groups ─────────────────────────────────────────────────────────────
367
368#[cfg(feature = "http2")]
369pub use self::r#async::{AsyncGroup, AsyncResource};
370#[cfg(feature = "http2")]
371pub(crate) use self::r#async::AsyncHandlerFn;
372
373#[cfg(feature = "http2")]
374mod r#async {
375    use std::future::Future;
376    use std::pin::Pin;
377    use std::sync::Arc;
378
379    use super::{join_path, resource_name, Guard, GroupRoute};
380    use crate::request::Request;
381    use crate::response::Response;
382    use crate::router::PathParams;
383    use crate::server::ConnectionInfo;
384
385    pub(crate) type BoxFuture = Pin<Box<dyn Future<Output = Response> + Send + 'static>>;
386    /// An async handler as groups store it.
387    pub(crate) type AsyncHandlerFn<S> = Arc<dyn Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> BoxFuture + Send + Sync>;
388
389    fn wrap_async<S: Send + Sync + 'static>(handler: AsyncHandlerFn<S>, guards: Arc<[Guard<S>]>) -> AsyncHandlerFn<S> {
390        if guards.is_empty() {
391            return handler;
392        }
393        Arc::new(move |request, params, connection, state| {
394            for guard in guards.iter() {
395                if let Err(response) = guard(&request, &connection, &state) {
396                    return Box::pin(async move { response });
397                }
398            }
399            handler(request, params, connection, state)
400        })
401    }
402
403    /// [`Group`](super::Group) for `AsyncAppWithState`: async handlers,
404    /// guards (sync checks before the handler runs). Middleware wraps the
405    /// whole app there (`app.wrap(layer)`), not a group.
406    pub struct AsyncGroup<S> {
407        prefix: String,
408        guards: Vec<Guard<S>>,
409        routes: Vec<GroupRoute<AsyncHandlerFn<S>>>,
410    }
411
412    impl<S: Send + Sync + 'static> AsyncGroup<S> {
413        pub(crate) fn new(prefix: &str) -> Self {
414            AsyncGroup { prefix: prefix.to_string(), guards: Vec::new(), routes: Vec::new() }
415        }
416
417        fn push(mut self, method: &'static str, pattern: &str, handler: AsyncHandlerFn<S>) -> Self {
418            self.routes.push(GroupRoute { method, pattern: join_path(&self.prefix, pattern), name: None, handler });
419            self
420        }
421
422        fn boxed<F, Fut>(handler: F) -> AsyncHandlerFn<S>
423        where
424            F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
425            Fut: Future<Output = Response> + Send + 'static,
426        {
427            Arc::new(move |r, p, c, s| Box::pin(handler(r, p, c, s)))
428        }
429
430        /// Run `guard` before every route of this group.
431        pub fn guard<G>(mut self, guard: G) -> Self
432        where
433            G: Fn(&Request, &ConnectionInfo, &S) -> Result<(), Response> + Send + Sync + 'static,
434        {
435            self.guards.push(Arc::new(guard));
436            self
437        }
438
439        /// Name the route registered last, for [`url_for`](crate::router::url_for).
440        pub fn name(mut self, name: &str) -> Self {
441            if let Some(route) = self.routes.last_mut() {
442                route.name = Some(name.to_string());
443            }
444            self
445        }
446
447        /// Register an async `GET` handler at `prefix + pattern`.
448        pub fn get<F, Fut>(self, pattern: &str, handler: F) -> Self
449        where
450            F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
451            Fut: Future<Output = Response> + Send + 'static,
452        {
453            self.push("GET", pattern, Self::boxed(handler))
454        }
455
456        /// Register an async `POST` handler at `prefix + pattern`.
457        pub fn post<F, Fut>(self, pattern: &str, handler: F) -> Self
458        where
459            F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
460            Fut: Future<Output = Response> + Send + 'static,
461        {
462            self.push("POST", pattern, Self::boxed(handler))
463        }
464
465        /// Register an async `PUT` handler at `prefix + pattern`.
466        pub fn put<F, Fut>(self, pattern: &str, handler: F) -> Self
467        where
468            F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
469            Fut: Future<Output = Response> + Send + 'static,
470        {
471            self.push("PUT", pattern, Self::boxed(handler))
472        }
473
474        /// Register an async `PATCH` handler at `prefix + pattern`.
475        pub fn patch<F, Fut>(self, pattern: &str, handler: F) -> Self
476        where
477            F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
478            Fut: Future<Output = Response> + Send + 'static,
479        {
480            self.push("PATCH", pattern, Self::boxed(handler))
481        }
482
483        /// Register an async `DELETE` handler at `prefix + pattern`.
484        pub fn delete<F, Fut>(self, pattern: &str, handler: F) -> Self
485        where
486            F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
487            Fut: Future<Output = Response> + Send + 'static,
488        {
489            self.push("DELETE", pattern, Self::boxed(handler))
490        }
491
492        /// Register a typed handler (`#[get("/path")]` & co.) at
493        /// `prefix + its pattern`.
494        pub fn route<H: crate::handler::AsyncHandler<S>>(self, handler: H) -> Self {
495            let handler = Arc::new(handler);
496            self.push(H::METHOD, H::PATTERN, Arc::new(move |r, p, c, s| handler.handle_async(r, p, c, s)))
497        }
498
499        /// A nested group at `prefix + pattern`.
500        pub fn group(mut self, pattern: &str, build: impl FnOnce(AsyncGroup<S>) -> AsyncGroup<S>) -> Self {
501            let inner = build(AsyncGroup::new(&join_path(&self.prefix, pattern)));
502            self.routes.extend(inner.finish());
503            self
504        }
505
506        /// The conventional routes of a resource at `prefix + path`.
507        pub fn resource(mut self, path: &str, build: impl FnOnce(AsyncResource<S>) -> AsyncResource<S>) -> Self {
508            let resource = build(AsyncResource::new(&join_path(&self.prefix, path)));
509            self.routes.extend(resource.finish());
510            self
511        }
512
513        pub(crate) fn finish(self) -> Vec<GroupRoute<AsyncHandlerFn<S>>> {
514            let guards: Arc<[Guard<S>]> = self.guards.into();
515            self.routes
516                .into_iter()
517                .map(|route| GroupRoute { handler: wrap_async(route.handler, Arc::clone(&guards)), ..route })
518                .collect()
519        }
520    }
521
522    /// [`Resource`](super::Resource) for `AsyncAppWithState`.
523    pub struct AsyncResource<S> {
524        group: AsyncGroup<S>,
525        name: String,
526        actions: Vec<(&'static str, &'static str, &'static str, AsyncHandlerFn<S>)>,
527    }
528
529    macro_rules! async_resource_action {
530        ($(#[$doc:meta])* $fn:ident, $action:literal, $method:literal, $suffix:literal) => {
531            $(#[$doc])*
532            pub fn $fn<F, Fut>(mut self, handler: F) -> Self
533            where
534                F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
535                Fut: Future<Output = Response> + Send + 'static,
536            {
537                self.actions.push(($action, $method, $suffix, AsyncGroup::<S>::boxed(handler)));
538                self
539            }
540        };
541    }
542
543    impl<S: Send + Sync + 'static> AsyncResource<S> {
544        fn new(path: &str) -> Self {
545            AsyncResource { name: resource_name(path), group: AsyncGroup::new(path), actions: Vec::new() }
546        }
547
548        /// The route names' prefix (default: the path's last segment).
549        pub fn name(mut self, name: &str) -> Self {
550            self.name = name.to_string();
551            self
552        }
553
554        /// Run `guard` before every route of this resource.
555        pub fn guard<G>(mut self, guard: G) -> Self
556        where
557            G: Fn(&Request, &ConnectionInfo, &S) -> Result<(), Response> + Send + Sync + 'static,
558        {
559            self.group = self.group.guard(guard);
560            self
561        }
562
563        async_resource_action!(/// `GET /posts`: the list.
564            index, "index", "GET", "");
565        async_resource_action!(/// `GET /posts/new`: the form for a new one.
566            new_form, "new", "GET", "/new");
567        async_resource_action!(/// `POST /posts`: create one.
568            create, "create", "POST", "");
569        async_resource_action!(/// `GET /posts/{id}`: one.
570            show, "show", "GET", "/{id}");
571        async_resource_action!(/// `GET /posts/{id}/edit`: the form to change one.
572            edit, "edit", "GET", "/{id}/edit");
573        async_resource_action!(/// `PUT` and `PATCH /posts/{id}`: change one.
574            update, "update", "PUT", "/{id}");
575        async_resource_action!(/// `DELETE /posts/{id}`: delete one.
576            destroy, "destroy", "DELETE", "/{id}");
577
578        /// More routes under the resource's path, with its guards.
579        pub fn extra(mut self, build: impl FnOnce(AsyncGroup<S>) -> AsyncGroup<S>) -> Self {
580            let prefix = self.group.prefix.clone();
581            let inner = build(AsyncGroup::new(&prefix));
582            self.group.routes.extend(inner.finish());
583            self
584        }
585
586        fn finish(mut self) -> Vec<GroupRoute<AsyncHandlerFn<S>>> {
587            let order = ["index", "new", "create", "show", "edit", "update", "destroy"];
588            self.actions.sort_by_key(|(action, ..)| order.iter().position(|a| a == action));
589            let mut group = self.group;
590            let mut extra = std::mem::take(&mut group.routes);
591            for (action, method, suffix, handler) in self.actions {
592                let pattern = join_path(&group.prefix, suffix);
593                let name = Some(format!("{}.{}", self.name, action));
594                if action == "update" {
595                    group.routes.push(GroupRoute { method: "PATCH", pattern: pattern.clone(), name: None, handler: Arc::clone(&handler) });
596                }
597                group.routes.push(GroupRoute { method, pattern, name, handler });
598            }
599            group.routes.append(&mut extra);
600            group.finish()
601        }
602    }
603}