Skip to main content

rainier_routing/
router.rs

1//! The [`Router`] — where routes are declared — and [`CompiledRouter`], what
2//! actually serves them.
3//!
4//! The split is deliberate. Declaring a route records the middleware it wants;
5//! compiling flattens each route's group and own middleware into one pipeline,
6//! **once**, at boot. A request then costs a match and a pipeline run, with no
7//! per-request middleware assembly.
8//!
9//! Middleware is attached by value, not by name — see
10//! [`MiddlewareStack`]. Compiling is still
11//! where a [deferred](rainier_middleware::MiddlewareStack::deferred) stage is
12//! built, so middleware that needs the container fails the boot rather than the
13//! first request that reaches it.
14//!
15//! ```
16//! use rainier_container::Container;
17//! use rainier_http::{Method, Request, Response};
18//! use rainier_routing::{GroupAttributes, Router};
19//!
20//! async fn index() -> &'static str { "all posts" }
21//! async fn show() -> &'static str { "one post" }
22//!
23//! # #[tokio::main] async fn main() -> rainier_support::Result<()> {
24//! let mut router = Router::new();
25//! router.get("/posts", index).name("posts.index");
26//!
27//! router.group(GroupAttributes::new().prefix("api").name("api."), |router| {
28//!     router.get("/posts/{post}", show).name("posts.show").where_number("post");
29//! });
30//!
31//! let compiled = router.compile(&Container::new())?;
32//!
33//! let response = compiled
34//!     .dispatch(Request::builder().method(Method::GET).uri("/api/posts/7").build())
35//!     .await;
36//! assert_eq!(response.status(), 200);
37//! # Ok(()) }
38//! ```
39
40use std::collections::HashMap;
41use std::sync::Arc;
42
43use rainier_container::Container;
44use rainier_http::{IntoResponse, Method, Request, Response};
45use rainier_middleware::{
46    ConcreteMiddleware, Destination, IntoMiddlewareStack, Middleware, MiddlewareStack, Pipeline,
47    ReadyPipeline,
48};
49use rainier_support::{BoxedFuture, Error, Result};
50
51use crate::handler::{IntoRouteHandler, RouteHandler};
52use crate::route::{normalise_uri, ParamConstraint, Route};
53
54/// Details of the route that matched, placed in the request's extensions so
55/// middleware and handlers can inspect it.
56#[derive(Debug, Clone)]
57pub struct MatchedRoute {
58    /// The route's name, if it has one.
59    pub name: Option<String>,
60    /// The URI pattern (not the concrete path).
61    pub uri: String,
62    /// The methods the route answers.
63    pub methods: Vec<Method>,
64}
65
66/// Attributes shared by every route declared inside a
67/// [`group`](Router::group).
68#[derive(Debug, Default, Clone)]
69pub struct GroupAttributes {
70    prefix: String,
71    name_prefix: String,
72    middleware: MiddlewareStack,
73    constraints: HashMap<String, ParamConstraint>,
74}
75
76impl GroupAttributes {
77    /// No attributes.
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    /// Prepend a URI prefix to every route in the group.
83    pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
84        self.prefix = prefix.into();
85        self
86    }
87
88    /// Prepend a name prefix (conventionally ending in `.`) to every *named*
89    /// route in the group.
90    pub fn name(mut self, prefix: impl Into<String>) -> Self {
91        self.name_prefix = prefix.into();
92        self
93    }
94
95    /// Apply middleware to every route in the group, outside their own.
96    ///
97    /// Takes the middleware itself, so a group is a function returning a
98    /// [`MiddlewareStack`] rather than a name in a registry:
99    ///
100    /// ```ignore
101    /// router.group(GroupAttributes::new().prefix("api").middleware(kernel::api()), |router| {
102    ///     router.get("/posts", index);
103    /// });
104    /// ```
105    pub fn middleware(mut self, middleware: impl IntoMiddlewareStack) -> Self {
106        self.middleware = self.middleware.with_stack(middleware.into_middleware_stack());
107        self
108    }
109
110    /// Apply a parameter constraint to every route in the group that does not
111    /// set its own.
112    pub fn where_param(mut self, param: impl Into<String>, constraint: ParamConstraint) -> Self {
113        self.constraints.insert(param.into(), constraint);
114        self
115    }
116}
117
118/// The route table under declaration.
119#[derive(Default)]
120pub struct Router {
121    routes: Vec<Route>,
122    fallback: Option<Arc<dyn RouteHandler>>,
123}
124
125impl Router {
126    /// An empty router.
127    pub fn new() -> Self {
128        Self::default()
129    }
130
131    // --- verbs -------------------------------------------------------------
132
133    /// Register a route for `methods`, returning it for further configuration.
134    pub fn add<H, Args>(
135        &mut self,
136        methods: Vec<Method>,
137        uri: impl Into<String>,
138        handler: H,
139    ) -> &mut Route
140    where
141        H: IntoRouteHandler<Args>,
142    {
143        self.add_erased(methods, uri, handler.into_route_handler())
144    }
145
146    /// Register a route from an already-erased handler.
147    ///
148    /// The path resource routes and other generated routes take, where the
149    /// handler was built dynamically rather than inferred from a function
150    /// signature.
151    pub fn add_erased(
152        &mut self,
153        methods: Vec<Method>,
154        uri: impl Into<String>,
155        handler: Arc<dyn RouteHandler>,
156    ) -> &mut Route {
157        self.routes.push(Route::new(methods, uri, handler));
158        self.routes.last_mut().expect("just pushed")
159    }
160
161    /// `GET` (and `HEAD`).
162    pub fn get<H, Args>(&mut self, uri: impl Into<String>, handler: H) -> &mut Route
163    where
164        H: IntoRouteHandler<Args>,
165    {
166        self.add(vec![Method::GET], uri, handler)
167    }
168
169    /// `POST`.
170    pub fn post<H, Args>(&mut self, uri: impl Into<String>, handler: H) -> &mut Route
171    where
172        H: IntoRouteHandler<Args>,
173    {
174        self.add(vec![Method::POST], uri, handler)
175    }
176
177    /// `PUT`.
178    pub fn put<H, Args>(&mut self, uri: impl Into<String>, handler: H) -> &mut Route
179    where
180        H: IntoRouteHandler<Args>,
181    {
182        self.add(vec![Method::PUT], uri, handler)
183    }
184
185    /// `PATCH`.
186    pub fn patch<H, Args>(&mut self, uri: impl Into<String>, handler: H) -> &mut Route
187    where
188        H: IntoRouteHandler<Args>,
189    {
190        self.add(vec![Method::PATCH], uri, handler)
191    }
192
193    /// `DELETE`.
194    pub fn delete<H, Args>(&mut self, uri: impl Into<String>, handler: H) -> &mut Route
195    where
196        H: IntoRouteHandler<Args>,
197    {
198        self.add(vec![Method::DELETE], uri, handler)
199    }
200
201    /// `OPTIONS`.
202    pub fn options<H, Args>(&mut self, uri: impl Into<String>, handler: H) -> &mut Route
203    where
204        H: IntoRouteHandler<Args>,
205    {
206        self.add(vec![Method::OPTIONS], uri, handler)
207    }
208
209    /// Every common method.
210    pub fn any<H, Args>(&mut self, uri: impl Into<String>, handler: H) -> &mut Route
211    where
212        H: IntoRouteHandler<Args>,
213    {
214        self.add(
215            vec![
216                Method::GET,
217                Method::POST,
218                Method::PUT,
219                Method::PATCH,
220                Method::DELETE,
221                Method::OPTIONS,
222            ],
223            uri,
224            handler,
225        )
226    }
227
228    /// A permanent redirect from `from` to `to`.
229    pub fn redirect(&mut self, from: impl Into<String>, to: impl Into<String>) -> &mut Route {
230        let to = to.into();
231        self.get(from, move || {
232            let to = to.clone();
233            async move { rainier_http::Redirect::permanent(to) }
234        })
235    }
236
237    /// The handler for requests that match nothing.
238    ///
239    /// Without one, an unmatched request is a plain `404`.
240    pub fn fallback<H, Args>(&mut self, handler: H)
241    where
242        H: IntoRouteHandler<Args>,
243    {
244        self.fallback = Some(handler.into_route_handler());
245    }
246
247    // --- groups ------------------------------------------------------------
248
249    /// Declare routes that share a prefix, a name prefix, middleware, or
250    /// parameter constraints.
251    ///
252    /// Groups nest: the attributes are applied to every route the closure
253    /// added, *after* any inner group has applied its own — so the outer
254    /// prefix ends up outermost and the outer middleware runs first.
255    pub fn group(&mut self, attributes: GroupAttributes, declare: impl FnOnce(&mut Router)) {
256        let start = self.routes.len();
257        declare(self);
258
259        for route in &mut self.routes[start..] {
260            route.prefix_with(&attributes.prefix);
261            route.prepend_middleware(&attributes.middleware);
262            route.prepend_name(&attributes.name_prefix);
263            route.add_constraints(&attributes.constraints);
264        }
265    }
266
267    /// Merge another router's routes into this one, keeping their order.
268    pub fn merge(&mut self, other: Router) {
269        self.routes.extend(other.routes);
270        if self.fallback.is_none() {
271            self.fallback = other.fallback;
272        }
273    }
274
275    // --- inspection --------------------------------------------------------
276
277    /// Every declared route, in declaration order.
278    pub fn routes(&self) -> &[Route] {
279        &self.routes
280    }
281
282    /// Mutable access to the declared routes.
283    pub fn routes_mut(&mut self) -> &mut [Route] {
284        &mut self.routes
285    }
286
287    /// How many routes are declared.
288    pub fn len(&self) -> usize {
289        self.routes.len()
290    }
291
292    /// Whether no routes are declared.
293    pub fn is_empty(&self) -> bool {
294        self.routes.is_empty()
295    }
296
297    /// The URI pattern of a named route.
298    pub fn uri_for(&self, name: &str) -> Option<&str> {
299        self.routes.iter().find(|route| route.route_name() == Some(name)).map(|route| route.uri())
300    }
301
302    /// Every named route, as `(name, uri)`.
303    pub fn named_routes(&self) -> Vec<(&str, &str)> {
304        self.routes
305            .iter()
306            .filter_map(|route| route.route_name().map(|name| (name, route.uri())))
307            .collect()
308    }
309
310    // --- compilation -------------------------------------------------------
311
312    /// The route table, without compiling anything.
313    ///
314    /// [`CompiledRouter::describe`] needs a container, because compiling
315    /// builds every middleware stage — and a `deferred` stage that resolves a
316    /// service will fail if that service is not bound. That is the right
317    /// behaviour before serving traffic and the wrong behaviour for a question
318    /// as harmless as "what routes are there?".
319    ///
320    /// So `route:list`, a documentation generator and a test asserting on the
321    /// table can all ask here. The middleware column is the **declared**
322    /// labels, which is what those callers wanted to read anyway.
323    pub fn describe(&self) -> Vec<RouteSummary> {
324        self.routes
325            .iter()
326            .map(|route| RouteSummary {
327                methods: route.methods().iter().map(|m| m.to_string()).collect(),
328                uri: route.uri().to_string(),
329                name: route.route_name().map(str::to_string),
330                middleware: route.middleware_stack().labels(),
331            })
332            .collect()
333    }
334
335    /// Build every route's pipeline.
336    ///
337    /// Takes no registry: a route holds its middleware, so there is nothing to
338    /// look up. It takes the **container**, because this is where every
339    /// [deferred](rainier_middleware::MiddlewareStack::deferred) stage is
340    /// built — the point in the lifecycle where the container is populated and
341    /// no request has arrived yet.
342    pub fn compile(self, container: &Container) -> Result<CompiledRouter> {
343        let mut duplicate_names: HashMap<&str, usize> = HashMap::new();
344        for route in &self.routes {
345            if let Some(name) = route.route_name() {
346                *duplicate_names.entry(name).or_insert(0) += 1;
347            }
348        }
349        if let Some((name, _)) = duplicate_names.into_iter().find(|(_, count)| *count > 1) {
350            return Err(Error::internal(format!(
351                "two routes are both named `{name}` — route names must be unique for URL \
352                 generation to be unambiguous"
353            )));
354        }
355
356        let mut compiled = Vec::with_capacity(self.routes.len());
357        for route in self.routes {
358            let pipeline = build_pipeline(&route, container)?;
359            compiled.push(CompiledRoute { route, pipeline });
360        }
361
362        let fallback = self.fallback.map(|handler| {
363            let destination: Arc<dyn Destination> = Arc::new(HandlerDestination(handler));
364            Pipeline::new().then_arc(destination)
365        });
366
367        Ok(CompiledRouter { routes: compiled, fallback })
368    }
369}
370
371fn build_pipeline(route: &Route, container: &Container) -> Result<ReadyPipeline> {
372    // Deferred stages are built here, which is why a failure names the route:
373    // "the container has no AuthManager" is a great deal less useful without
374    // knowing which route asked for one.
375    let resolved = route.middleware_stack().resolve(container).map_err(|e| {
376        Error::internal(format!(
377            "route `{} {}`: {e}",
378            route.methods().first().map(Method::as_str).unwrap_or("?"),
379            route.uri()
380        ))
381    })?;
382
383    // Exclusion happens after resolution because a deferred stage has no
384    // identity until it is built — and by type, because a value has no other.
385    let excluded = route.excluded_middleware();
386    let resolved: Vec<Arc<dyn Middleware>> = resolved
387        .into_iter()
388        .filter(|stage| !excluded.contains(&ConcreteMiddleware::concrete_type_id(&**stage)))
389        .collect();
390
391    let destination: Arc<dyn Destination> =
392        Arc::new(HandlerDestination(Arc::clone(route.handler())));
393    Ok(Pipeline::new().through_all(resolved).then_arc(destination))
394}
395
396/// Adapts a [`RouteHandler`] to the pipeline's [`Destination`].
397struct HandlerDestination(Arc<dyn RouteHandler>);
398
399impl Destination for HandlerDestination {
400    fn call(&self, request: Request) -> BoxedFuture<Response> {
401        self.0.call(request)
402    }
403}
404
405struct CompiledRoute {
406    route: Route,
407    pipeline: ReadyPipeline,
408}
409
410/// A router with every route's middleware resolved and pipeline built.
411pub struct CompiledRouter {
412    routes: Vec<CompiledRoute>,
413    fallback: Option<ReadyPipeline>,
414}
415
416impl CompiledRouter {
417    /// Match `request` to a route and run it.
418    ///
419    /// Routes are tried in **declaration order** and the first match
420    /// wins. That means `/posts/create` must be declared before
421    /// `/posts/{post}`, and it is why declaration order is preserved rather
422    /// than the table being sorted by specificity.
423    pub async fn dispatch(&self, mut request: Request) -> Response {
424        let path = request.path().to_string();
425        let mut allowed: Vec<Method> = Vec::new();
426
427        for compiled in &self.routes {
428            let Some(params) = compiled.route.match_path(&path) else {
429                continue;
430            };
431
432            if !compiled.route.accepts(request.method()) {
433                for method in compiled.route.methods() {
434                    if !allowed.contains(method) {
435                        allowed.push(method.clone());
436                    }
437                }
438                continue;
439            }
440
441            // Route parameters and the matched route go in *before* the
442            // pipeline runs, so middleware can read them — an authorisation
443            // middleware needs `{post}` as much as the handler does.
444            request.set_route_params(params);
445            request.extensions_mut().insert(MatchedRoute {
446                name: compiled.route.route_name().map(str::to_string),
447                uri: compiled.route.uri().to_string(),
448                methods: compiled.route.methods().to_vec(),
449            });
450
451            return compiled.pipeline.run(request).await;
452        }
453
454        if !allowed.is_empty() {
455            let allow = allowed.iter().map(Method::as_str).collect::<Vec<_>>().join(", ");
456            return Error::new(
457                rainier_support::ErrorKind::MethodNotAllowed,
458                format!("The {} method is not supported for this route.", request.method()),
459            )
460            .into_response()
461            .with_header("allow", &allow);
462        }
463
464        match &self.fallback {
465            Some(fallback) => fallback.run(request).await,
466            None => Error::not_found(format!("No route matches {path}")).into_response(),
467        }
468    }
469
470    /// A `route:list`-style summary: `(methods, uri, name, middleware)`.
471    pub fn describe(&self) -> Vec<RouteSummary> {
472        self.routes
473            .iter()
474            .map(|compiled| RouteSummary {
475                methods: compiled.route.methods().iter().map(|m| m.to_string()).collect(),
476                uri: compiled.route.uri().to_string(),
477                name: compiled.route.route_name().map(str::to_string),
478                middleware: compiled.pipeline.stage_names(),
479            })
480            .collect()
481    }
482
483    /// The URI pattern of a named route.
484    pub fn uri_for(&self, name: &str) -> Option<&str> {
485        self.routes
486            .iter()
487            .find(|compiled| compiled.route.route_name() == Some(name))
488            .map(|compiled| compiled.route.uri())
489    }
490
491    /// Every named route, as `(name, uri)` — what the URL generator is built
492    /// from.
493    pub fn named_routes(&self) -> Vec<(String, String)> {
494        self.routes
495            .iter()
496            .filter_map(|compiled| {
497                compiled
498                    .route
499                    .route_name()
500                    .map(|name| (name.to_string(), compiled.route.uri().to_string()))
501            })
502            .collect()
503    }
504
505    /// How many routes are served.
506    pub fn len(&self) -> usize {
507        self.routes.len()
508    }
509
510    /// Whether nothing is served.
511    pub fn is_empty(&self) -> bool {
512        self.routes.is_empty()
513    }
514}
515
516impl std::fmt::Debug for CompiledRouter {
517    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518        f.debug_struct("CompiledRouter")
519            .field("routes", &self.len())
520            .field("fallback", &self.fallback.is_some())
521            .finish()
522    }
523}
524
525/// One row of a `route:list`.
526#[derive(Debug, Clone, PartialEq, Eq)]
527pub struct RouteSummary {
528    /// The methods the route answers.
529    pub methods: Vec<String>,
530    /// The URI pattern.
531    pub uri: String,
532    /// The route's name, if any.
533    pub name: Option<String>,
534    /// The middleware in its pipeline, outermost first.
535    pub middleware: Vec<&'static str>,
536}
537
538/// A `404` handler, useful as an explicit fallback.
539pub async fn not_found() -> Response {
540    Error::not_found("Not Found").into_response()
541}
542
543/// Normalise a URI the way route declaration does. Re-exported for callers
544/// building paths by hand.
545pub fn normalise(uri: &str) -> String {
546    normalise_uri(uri)
547}
548
549#[cfg(test)]
550mod tests {
551    use super::*;
552    use crate::handler::Req;
553    use rainier_http::StatusCode;
554    use rainier_middleware::{Middleware, MiddlewareStack, Next};
555
556    /// A service nothing binds.
557    struct Unbound;
558
559    /// A middleware built from [`Unbound`], so a `deferred` stage naming it
560    /// cannot be built.
561    struct Nothing;
562
563    #[async_trait::async_trait]
564    impl Middleware for Nothing {
565        async fn handle(&self, request: Request, next: Next) -> Response {
566            next.run(request).await
567        }
568    }
569
570    async fn index() -> &'static str {
571        "index"
572    }
573    async fn show(request: Req) -> String {
574        format!("show:{}", request.route_param("post").unwrap_or("?"))
575    }
576    async fn store() -> &'static str {
577        "store"
578    }
579
580    async fn body_of(response: Response) -> String {
581        String::from_utf8(response.into_http().into_body().collect().await.unwrap().to_vec())
582            .unwrap()
583    }
584
585    fn get(uri: &str) -> Request {
586        Request::builder().method(Method::GET).uri(uri).build()
587    }
588
589    fn compile(router: Router) -> CompiledRouter {
590        router.compile(&Container::new()).expect("compiles")
591    }
592
593    #[tokio::test]
594    async fn dispatches_to_a_static_route() {
595        let mut router = Router::new();
596        router.get("/posts", index);
597
598        let response = compile(router).dispatch(get("/posts")).await;
599        assert_eq!(body_of(response).await, "index");
600    }
601
602    #[tokio::test]
603    async fn captures_route_parameters() {
604        let mut router = Router::new();
605        router.get("/posts/{post}", show);
606
607        let response = compile(router).dispatch(get("/posts/7")).await;
608        assert_eq!(body_of(response).await, "show:7");
609    }
610
611    #[tokio::test]
612    async fn an_unmatched_path_is_a_404() {
613        let mut router = Router::new();
614        router.get("/posts", index);
615
616        let response = compile(router).dispatch(get("/nope")).await;
617        assert_eq!(response.status(), StatusCode::NOT_FOUND);
618    }
619
620    #[tokio::test]
621    async fn a_matched_path_with_the_wrong_method_is_a_405_listing_what_is_allowed() {
622        let mut router = Router::new();
623        router.get("/posts", index);
624        router.post("/posts", store);
625
626        let request = Request::builder().method(Method::DELETE).uri("/posts").build();
627        let response = compile(router).dispatch(request).await;
628
629        assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED);
630        let allow = response.header("allow").unwrap().to_string();
631        assert!(allow.contains("GET"), "{allow}");
632        assert!(allow.contains("POST"), "{allow}");
633    }
634
635    #[tokio::test]
636    async fn the_first_declared_route_wins() {
637        let mut router = Router::new();
638        router.get("/posts/create", || async { "create form" });
639        router.get("/posts/{post}", show);
640
641        let compiled = compile(router);
642        assert_eq!(body_of(compiled.dispatch(get("/posts/create")).await).await, "create form");
643        assert_eq!(body_of(compiled.dispatch(get("/posts/9")).await).await, "show:9");
644    }
645
646    #[tokio::test]
647    async fn a_constraint_lets_a_later_route_win() {
648        let mut router = Router::new();
649        router.get("/posts/{post}", show).where_number("post");
650        router.get("/posts/create", || async { "create form" });
651
652        let compiled = compile(router);
653        // `create` fails the numeric constraint, so matching falls through.
654        assert_eq!(body_of(compiled.dispatch(get("/posts/create")).await).await, "create form");
655        assert_eq!(body_of(compiled.dispatch(get("/posts/9")).await).await, "show:9");
656    }
657
658    #[tokio::test]
659    async fn the_fallback_catches_everything_unmatched() {
660        let mut router = Router::new();
661        router.get("/posts", index);
662        router.fallback(|| async { "nothing here" });
663
664        let response = compile(router).dispatch(get("/anywhere/else")).await;
665        assert_eq!(body_of(response).await, "nothing here");
666    }
667
668    #[tokio::test]
669    async fn groups_apply_a_prefix_and_a_name() {
670        let mut router = Router::new();
671        router.group(GroupAttributes::new().prefix("api/v1").name("api."), |router| {
672            router.get("/posts", index).name("posts.index");
673        });
674
675        let compiled = compile(router);
676        assert_eq!(compiled.uri_for("api.posts.index"), Some("/api/v1/posts"));
677        assert_eq!(compiled.dispatch(get("/api/v1/posts")).await.status(), StatusCode::OK);
678        assert_eq!(compiled.dispatch(get("/posts")).await.status(), StatusCode::NOT_FOUND);
679    }
680
681    #[tokio::test]
682    async fn groups_nest_outermost_first() {
683        let mut router = Router::new();
684        router.group(GroupAttributes::new().prefix("api").name("api."), |router| {
685            router.group(GroupAttributes::new().prefix("v1").name("v1."), |router| {
686                router.get("/posts", index).name("posts.index");
687            });
688        });
689
690        let compiled = compile(router);
691        assert_eq!(compiled.uri_for("api.v1.posts.index"), Some("/api/v1/posts"));
692    }
693
694    #[tokio::test]
695    async fn group_constraints_apply_to_every_route_that_lacks_its_own() {
696        let mut router = Router::new();
697        router.group(
698            GroupAttributes::new().where_param("post", ParamConstraint::Number),
699            |router| {
700                router.get("/posts/{post}", show);
701            },
702        );
703
704        let compiled = compile(router);
705        assert_eq!(compiled.dispatch(get("/posts/7")).await.status(), StatusCode::OK);
706        assert_eq!(compiled.dispatch(get("/posts/abc")).await.status(), StatusCode::NOT_FOUND);
707    }
708
709    struct Tag(&'static str);
710
711    #[async_trait::async_trait]
712    impl Middleware for Tag {
713        async fn handle(&self, request: Request, next: Next) -> Response {
714            next.run(request).await.with_added_header("x-tag", self.0)
715        }
716        fn name(&self) -> &'static str {
717            "Tag"
718        }
719    }
720
721    /// A second middleware type, so exclusion has something to discriminate on.
722    struct Audit;
723
724    #[async_trait::async_trait]
725    impl Middleware for Audit {
726        async fn handle(&self, request: Request, next: Next) -> Response {
727            next.run(request).await.with_added_header("x-tag", "audit")
728        }
729    }
730
731    #[tokio::test]
732    async fn route_and_group_middleware_both_run() {
733        let mut router = Router::new();
734        router.group(GroupAttributes::new().middleware(Tag("group")), |router| {
735            router.get("/posts", index).middleware(Tag("route"));
736        });
737
738        let compiled = router.compile(&Container::new()).unwrap();
739        let response = compiled.dispatch(get("/posts")).await;
740        let tags: Vec<_> =
741            response.headers().get_all("x-tag").iter().map(|v| v.to_str().unwrap()).collect();
742        assert_eq!(tags.len(), 2);
743    }
744
745    #[tokio::test]
746    async fn a_tuple_attaches_several_in_order() {
747        let mut router = Router::new();
748        router.get("/posts", index).middleware((Tag("first"), Audit));
749
750        let compiled = router.compile(&Container::new()).unwrap();
751        let response = compiled.dispatch(get("/posts")).await;
752        let tags: Vec<_> =
753            response.headers().get_all("x-tag").iter().map(|v| v.to_str().unwrap()).collect();
754        assert_eq!(tags, vec!["audit", "first"], "the outermost runs last on the way out");
755    }
756
757    #[tokio::test]
758    async fn a_route_can_opt_out_of_group_middleware_by_type() {
759        let mut router = Router::new();
760        router.group(GroupAttributes::new().middleware((Tag("group"), Audit)), |router| {
761            router.get("/open", index).without_middleware::<Audit>();
762            router.get("/closed", index);
763        });
764
765        let compiled = router.compile(&Container::new()).unwrap();
766
767        let open = compiled.dispatch(get("/open")).await;
768        let open_tags: Vec<_> =
769            open.headers().get_all("x-tag").iter().map(|v| v.to_str().unwrap()).collect();
770        assert_eq!(open_tags, vec!["group"], "only the excluded type is dropped");
771
772        let closed = compiled.dispatch(get("/closed")).await;
773        assert_eq!(closed.headers().get_all("x-tag").iter().count(), 2);
774    }
775
776    #[test]
777    fn a_deferred_stage_that_cannot_be_built_names_the_route() {
778        // The replacement for "unknown middleware alias": the only way a route
779        // can now fail to compile is middleware that needs something the
780        // container does not have.
781        let mut router = Router::new();
782        router.get("/posts", index).middleware(
783            MiddlewareStack::new()
784                .deferred(|_| Err::<Audit, _>(Error::internal("AuthManager is not bound"))),
785        );
786
787        let err = router.compile(&Container::new()).err().expect("compilation should fail");
788        assert!(err.message().contains("/posts"), "{}", err.message());
789        assert!(err.message().contains("Audit"), "{}", err.message());
790        assert!(err.message().contains("AuthManager"), "{}", err.message());
791    }
792
793    #[test]
794    fn duplicate_route_names_are_rejected() {
795        let mut router = Router::new();
796        router.get("/a", index).name("same");
797        router.get("/b", index).name("same");
798
799        let err = router.compile(&Container::new()).err().expect("should fail");
800        assert!(err.message().contains("`same`"), "{}", err.message());
801    }
802
803    #[tokio::test]
804    async fn the_matched_route_is_readable_from_the_request() {
805        async fn reveal(request: Req) -> String {
806            request
807                .extension::<MatchedRoute>()
808                .map(|route| format!("{}|{:?}", route.uri, route.name))
809                .unwrap_or_default()
810        }
811
812        let mut router = Router::new();
813        router.get("/posts/{post}", reveal).name("posts.show");
814
815        let response = compile(router).dispatch(get("/posts/1")).await;
816        assert_eq!(body_of(response).await, "/posts/{post}|Some(\"posts.show\")");
817    }
818
819    #[tokio::test]
820    async fn redirect_routes_send_a_location() {
821        let mut router = Router::new();
822        router.redirect("/old", "/new");
823
824        let response = compile(router).dispatch(get("/old")).await;
825        assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY);
826        assert_eq!(response.header("location"), Some("/new"));
827    }
828
829    #[tokio::test]
830    async fn merging_keeps_both_route_sets() {
831        let mut web = Router::new();
832        web.get("/", index).name("home");
833
834        let mut api = Router::new();
835        api.get("/api/posts", index).name("api.posts");
836
837        web.merge(api);
838        let compiled = compile(web);
839        assert_eq!(compiled.len(), 2);
840        assert_eq!(compiled.dispatch(get("/api/posts")).await.status(), StatusCode::OK);
841    }
842
843    #[tokio::test]
844    async fn describe_reports_the_table() {
845        let mut router = Router::new();
846        router.get("/posts", index).name("posts.index").middleware(Tag("route"));
847
848        let compiled = router.compile(&Container::new()).unwrap();
849        let rows = compiled.describe();
850        assert_eq!(rows.len(), 1);
851        assert_eq!(rows[0].uri, "/posts");
852        assert_eq!(rows[0].name.as_deref(), Some("posts.index"));
853        assert!(rows[0].methods.contains(&"GET".to_string()));
854        assert_eq!(rows[0].middleware, vec!["Tag"]);
855    }
856
857    #[tokio::test]
858    async fn head_is_served_wherever_get_is() {
859        let mut router = Router::new();
860        router.get("/posts", index);
861
862        let request = Request::builder().method(Method::HEAD).uri("/posts").build();
863        assert_eq!(compile(router).dispatch(request).await.status(), StatusCode::OK);
864    }
865
866    #[test]
867    fn an_uncompiled_router_can_still_describe_itself() {
868        // The point: no container, so nothing resolves and nothing can fail.
869        let mut router = Router::new();
870        router.get("/", || async { "home" }).name("home");
871        router.post("/posts", || async { "create" }).name("posts.store").middleware(
872            // A stage that needs a service nothing has bound: compiling this
873            // router would fail, and describing it must not.
874            MiddlewareStack::new().resolved(|_: Arc<Unbound>| Nothing),
875        );
876
877        let table = router.describe();
878
879        assert_eq!(table.len(), 2);
880        assert_eq!(table[0].uri, "/");
881        assert_eq!(table[0].name.as_deref(), Some("home"));
882        assert_eq!(table[1].methods, vec!["POST"]);
883        assert_eq!(table[1].middleware.len(), 1);
884
885        // And the proof that this is worth having: compiling cannot answer.
886        assert!(router.compile(&Container::new()).is_err());
887    }
888
889    #[tokio::test]
890    async fn describing_uncompiled_matches_describing_compiled() {
891        // Two implementations of the same table would drift. This is the test
892        // that notices.
893        let mut router = Router::new();
894        router.get("/", || async { "home" }).name("home");
895        router.put("/posts/{post}", || async { "update" }).name("posts.update");
896
897        let before = router.describe();
898        let after = router.compile(&Container::new()).unwrap().describe();
899
900        assert_eq!(before.len(), after.len());
901        for (before, after) in before.iter().zip(after.iter()) {
902            assert_eq!(before.methods, after.methods);
903            assert_eq!(before.uri, after.uri);
904            assert_eq!(before.name, after.name);
905        }
906    }
907}