Skip to main content

rama_http/service/web/
router.rs

1#![expect(
2    clippy::allow_attributes,
3    reason = "macro-generated `#[allow]` attributes whose underlying lints fire only for some expansions"
4)]
5
6use std::{cmp::Reverse, convert::Infallible, error::Error, fmt};
7
8use rama_core::{
9    Layer,
10    extensions::{Extensions, ExtensionsRef},
11    layer::MapResult,
12    matcher::Matcher,
13    service::{BoxService, Service},
14    telemetry::tracing,
15};
16use rama_http_types::Method;
17use rama_http_types::{Body, OriginalRouterUri, StatusCode};
18use rama_net::uri::{
19    PathPattern, PathPatternSegmentKind, PathPatternSegmentSpecificity, PathRouter,
20};
21use rama_utils::collections::NonEmptySmallVec;
22
23use crate::{
24    Request, Response,
25    headers::Allow,
26    matcher::path::{compile_pattern, match_pattern},
27    matcher::{HttpMatcher, MethodMatcher, UriParams},
28    service::web::{
29        IntoEndpointService, IntoEndpointServiceWithState,
30        response::{ErrorResponse, Headers, IntoResponse},
31    },
32};
33
34/// Default endpoint layer for the router.
35/// It converts Output and Error types of endpoints using [`IntoResponse`] trait,
36/// same as [`ErrorHandlerLayer`], except it returns [`RouterError`] instead of [`Infallible`]
37/// to fit default Router.
38///
39/// [`ErrorHandlerLayer`]: crate::layer::error_handling::ErrorHandlerLayer
40#[derive(Debug, Clone, Default)]
41#[non_exhaustive]
42pub struct DefaultEndpointLayer;
43
44impl<S> Layer<S> for DefaultEndpointLayer
45where
46    S: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
47{
48    type Service = MapResult<S, fn(Result<S::Output, S::Error>) -> Result<Response, RouterError>>;
49
50    fn layer(&self, inner: S) -> Self::Service {
51        MapResult::new(inner, |res| match res {
52            Ok(v) => Ok(v.into_response()),
53            Err(err) => Ok(err.into().into_response()),
54        })
55    }
56}
57
58/// A basic router that can be used to route requests to different services based on the request path.
59///
60/// Each route compiles to a [`PathPattern`]; on lookup the most-specific
61/// matching pattern wins (static segments beat captures beat catch-alls).
62/// Nested services are mounted under a prefix via a typed [`PathRouter`].
63#[allow(unused)]
64pub struct Router<State = (), Layer = DefaultEndpointLayer, O = Response, E = RouterError> {
65    routes: Vec<RouteEntry<O, E>>,
66    sub_services: Option<PathRouter<SubService<O, E>>>,
67    not_found: Option<BoxService<Request, O, E>>,
68    layer: Layer,
69    state: State,
70}
71
72/// One registered route path: its compiled pattern, a specificity key (kept
73/// sorted most-specific-first across `routes`), and the per-method handlers
74/// sharing this path.
75struct RouteEntry<O, E> {
76    pattern: PathPattern,
77    /// Per-segment specificity ranks (static=2, capture=1, catch-all=0);
78    /// higher under `Vec`'s ordering = more specific.
79    specificity: Vec<SegmentSpecificityRank>,
80    handlers: Vec<(HttpMatcher<Body>, BoxService<Request, O, E>)>,
81}
82
83/// Specificity rank of one segment. Literal beats dynamic beats catch-all; for
84/// two dynamic segments, more literal bytes and fewer wildcard/capture/optional
85/// parts wins (e.g. `{file}.json` beats `{file}`).
86#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
87struct SegmentSpecificityRank {
88    kind: u8,
89    literal_bytes: usize,
90    fewer_dynamic_parts: Reverse<usize>,
91    fewer_optional_parts: Reverse<usize>,
92}
93
94fn rank(spec: PathPatternSegmentSpecificity) -> SegmentSpecificityRank {
95    let kind = match spec.kind {
96        PathPatternSegmentKind::Literal => 2,
97        PathPatternSegmentKind::Dynamic => 1,
98        PathPatternSegmentKind::CatchAll => 0,
99    };
100    SegmentSpecificityRank {
101        kind,
102        literal_bytes: spec.literal_bytes,
103        fewer_dynamic_parts: Reverse(spec.dynamic_parts),
104        fewer_optional_parts: Reverse(spec.optional_parts),
105    }
106}
107
108/// Per-segment specificity key for a compiled pattern. The pattern itself is
109/// the authority on what each segment is — the router never parses the syntax.
110fn specificity_of(pattern: &PathPattern) -> Vec<SegmentSpecificityRank> {
111    pattern.segment_specificity().map(rank).collect()
112}
113
114impl<S, L, O, E> std::fmt::Debug for Router<S, L, O, E> {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("Router").finish()
117    }
118}
119
120impl<O, E> Router<(), DefaultEndpointLayer, O, E> {
121    /// create a new router.
122    #[must_use]
123    pub fn new() -> Self {
124        Self::new_with_state(())
125    }
126}
127
128impl<State, O, E> Router<State, DefaultEndpointLayer, O, E>
129where
130    State: Send + Sync + Clone + 'static,
131{
132    #[must_use]
133    /// Create a new router with state
134    pub fn new_with_state(state: State) -> Self {
135        Self {
136            routes: Vec::new(),
137            sub_services: None,
138            not_found: None,
139            layer: Default::default(),
140            state,
141        }
142    }
143}
144
145impl<State, L, O, E> Router<State, L, O, E>
146where
147    State: Send + Sync + Clone + 'static,
148{
149    /// Get reference to the state.
150    #[inline]
151    pub fn state(&self) -> &State {
152        &self.state
153    }
154
155    /// Apply `layer` to every endpoint registered after this call.
156    ///
157    /// Routes registered before this call keep whatever layer was in effect at the time of registration.
158    pub fn with_endpoint_layer<N>(self, layer: N) -> Router<State, N, O, E> {
159        Router {
160            routes: self.routes,
161            sub_services: self.sub_services,
162            not_found: self.not_found,
163            layer,
164            state: self.state,
165        }
166    }
167
168    /// Apply [`DefaultEndpointLayer`] to every endpoint registered after this call.
169    ///
170    /// Routes registered before this call keep whatever layer was in effect at the time of registration.
171    pub fn with_default_endpoint_layer(self) -> Router<State, DefaultEndpointLayer, O, E> {
172        Router {
173            routes: self.routes,
174            sub_services: self.sub_services,
175            not_found: self.not_found,
176            layer: DefaultEndpointLayer,
177            state: self.state,
178        }
179    }
180
181    /// add a GET route to the router.
182    /// the path can contain parameters, e.g. `/users/{id}`.
183    /// the path can also contain a catch call, e.g. `/assets/{*path}`.
184    #[must_use]
185    #[inline]
186    pub fn with_get<I, T>(self, path: impl AsRef<str>, service: I) -> Self
187    where
188        I: IntoEndpointServiceWithState<T, State>,
189        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
190    {
191        let matcher = HttpMatcher::method_get();
192        self.with_match_route(path, matcher, service)
193    }
194
195    /// add a GET route to the router.
196    /// the path can contain parameters, e.g. `/users/{id}`.
197    /// the path can also contain a catch call, e.g. `/assets/{*path}`.
198    #[inline]
199    pub fn set_get<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
200    where
201        I: IntoEndpointServiceWithState<T, State>,
202        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
203    {
204        let matcher = HttpMatcher::method_get();
205        self.set_match_route(path, matcher, service)
206    }
207
208    /// add a POST route to the router.
209    #[must_use]
210    #[inline]
211    pub fn with_post<I, T>(self, path: impl AsRef<str>, service: I) -> Self
212    where
213        I: IntoEndpointServiceWithState<T, State>,
214        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
215    {
216        let matcher = HttpMatcher::method_post();
217        self.with_match_route(path, matcher, service)
218    }
219
220    /// add a POST route to the router.
221    #[inline]
222    pub fn set_post<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
223    where
224        I: IntoEndpointServiceWithState<T, State>,
225        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
226    {
227        let matcher = HttpMatcher::method_post();
228        self.set_match_route(path, matcher, service)
229    }
230
231    /// add a PUT route to the router.
232    #[must_use]
233    #[inline]
234    pub fn with_put<I, T>(self, path: impl AsRef<str>, service: I) -> Self
235    where
236        I: IntoEndpointServiceWithState<T, State>,
237        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
238    {
239        let matcher = HttpMatcher::method_put();
240        self.with_match_route(path, matcher, service)
241    }
242
243    /// add a PUT route to the router.
244    #[inline]
245    pub fn set_put<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
246    where
247        I: IntoEndpointServiceWithState<T, State>,
248        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
249    {
250        let matcher = HttpMatcher::method_put();
251        self.set_match_route(path, matcher, service)
252    }
253
254    /// add a DELETE route to the router.
255    #[must_use]
256    #[inline]
257    pub fn with_delete<I, T>(self, path: impl AsRef<str>, service: I) -> Self
258    where
259        I: IntoEndpointServiceWithState<T, State>,
260        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
261    {
262        let matcher = HttpMatcher::method_delete();
263        self.with_match_route(path, matcher, service)
264    }
265
266    /// add a DELETE route to the router.
267    #[inline]
268    pub fn set_delete<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
269    where
270        I: IntoEndpointServiceWithState<T, State>,
271        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
272    {
273        let matcher = HttpMatcher::method_delete();
274        self.set_match_route(path, matcher, service)
275    }
276
277    /// add a PATCH route to the router.
278    #[must_use]
279    #[inline]
280    pub fn with_patch<I, T>(self, path: impl AsRef<str>, service: I) -> Self
281    where
282        I: IntoEndpointServiceWithState<T, State>,
283        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
284    {
285        let matcher = HttpMatcher::method_patch();
286        self.with_match_route(path, matcher, service)
287    }
288
289    /// add a PATCH route to the router.
290    #[inline]
291    pub fn set_patch<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
292    where
293        I: IntoEndpointServiceWithState<T, State>,
294        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
295    {
296        let matcher = HttpMatcher::method_patch();
297        self.set_match_route(path, matcher, service)
298    }
299
300    /// add a HEAD route to the router.
301    #[must_use]
302    #[inline]
303    pub fn with_head<I, T>(self, path: impl AsRef<str>, service: I) -> Self
304    where
305        I: IntoEndpointServiceWithState<T, State>,
306        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
307    {
308        let matcher = HttpMatcher::method_head();
309        self.with_match_route(path, matcher, service)
310    }
311
312    /// add a HEAD route to the router.
313    #[inline]
314    pub fn set_head<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
315    where
316        I: IntoEndpointServiceWithState<T, State>,
317        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
318    {
319        let matcher = HttpMatcher::method_head();
320        self.set_match_route(path, matcher, service)
321    }
322
323    /// add a OPTIONS route to the router.
324    #[must_use]
325    #[inline]
326    pub fn with_options<I, T>(self, path: impl AsRef<str>, service: I) -> Self
327    where
328        I: IntoEndpointServiceWithState<T, State>,
329        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
330    {
331        let matcher = HttpMatcher::method_options();
332        self.with_match_route(path, matcher, service)
333    }
334
335    /// add a OPTIONS route to the router.
336    #[inline]
337    pub fn set_options<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
338    where
339        I: IntoEndpointServiceWithState<T, State>,
340        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
341    {
342        let matcher = HttpMatcher::method_options();
343        self.set_match_route(path, matcher, service)
344    }
345
346    /// add a TRACE route to the router.
347    #[must_use]
348    #[inline]
349    pub fn with_trace<I, T>(self, path: impl AsRef<str>, service: I) -> Self
350    where
351        I: IntoEndpointServiceWithState<T, State>,
352        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
353    {
354        let matcher = HttpMatcher::method_trace();
355        self.with_match_route(path, matcher, service)
356    }
357
358    /// add a TRACE route to the router.
359    #[inline]
360    pub fn set_trace<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
361    where
362        I: IntoEndpointServiceWithState<T, State>,
363        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
364    {
365        let matcher = HttpMatcher::method_trace();
366        self.set_match_route(path, matcher, service)
367    }
368
369    /// add a CONNECT route to the router.
370    #[must_use]
371    #[inline]
372    pub fn with_connect<I, T>(self, path: impl AsRef<str>, service: I) -> Self
373    where
374        I: IntoEndpointServiceWithState<T, State>,
375        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
376    {
377        let matcher = HttpMatcher::method_connect();
378        self.with_match_route(path, matcher, service)
379    }
380
381    /// add a CONNECT route to the router.
382    #[inline]
383    pub fn set_connect<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
384    where
385        I: IntoEndpointServiceWithState<T, State>,
386        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
387    {
388        let matcher = HttpMatcher::method_connect();
389        self.set_match_route(path, matcher, service)
390    }
391
392    /// add a QUERY route to the router.
393    ///
394    /// QUERY ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)) is a safe,
395    /// idempotent method whose request content defines the query.
396    #[must_use]
397    #[inline]
398    pub fn with_query<I, T>(self, path: impl AsRef<str>, service: I) -> Self
399    where
400        I: IntoEndpointServiceWithState<T, State>,
401        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
402    {
403        let matcher = HttpMatcher::method_query();
404        self.with_match_route(path, matcher, service)
405    }
406
407    /// add a QUERY route to the router.
408    #[inline]
409    pub fn set_query<I, T>(&mut self, path: impl AsRef<str>, service: I) -> &mut Self
410    where
411        I: IntoEndpointServiceWithState<T, State>,
412        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
413    {
414        let matcher = HttpMatcher::method_query();
415        self.set_match_route(path, matcher, service)
416    }
417
418    /// register a nested router under a prefix (path).
419    ///
420    /// The prefix is used to match the request path and strip it from the request URI.
421    ///
422    /// Note: this sub-router is configured with the same State this router has.
423    #[must_use]
424    #[inline]
425    pub fn with_sub_router_make_fn<Layer>(
426        mut self,
427        prefix: impl AsRef<str>,
428        configure_router: impl FnOnce(Self) -> Router<State, Layer, O, E>,
429    ) -> Self
430    where
431        L: Clone,
432        Router<State, Layer, O, E>: Service<Request, Output = O, Error = E>,
433    {
434        self.set_sub_router_make_fn(prefix, configure_router);
435        self
436    }
437
438    /// register a nested router under a prefix (path).
439    ///
440    /// The prefix is used to match the request path and strip it from the request URI.
441    ///
442    /// Note: this sub-router is configured with the same State this router has.
443    pub fn set_sub_router_make_fn<Layer>(
444        &mut self,
445        prefix: impl AsRef<str>,
446        configure_router: impl FnOnce(Self) -> Router<State, Layer, O, E>,
447    ) -> &mut Self
448    where
449        L: Clone,
450        Router<State, Layer, O, E>: Service<Request, Output = O, Error = E>,
451    {
452        let router = Self {
453            routes: Vec::new(),
454            sub_services: None,
455            not_found: None,
456            layer: self.layer.clone(),
457            state: self.state.clone(),
458        };
459        let router = configure_router(router);
460        let nested = router.boxed();
461        self.set_sub_service_inner(prefix, nested)
462    }
463
464    /// Register a nested endpoint service under a prefix (path).
465    ///
466    /// The prefix is used to match the request path and strip it from the request URI.
467    /// Endpoint layer is applied to this sub-service.
468    ///
469    /// Warning: If a sub-service is a plain [`Service`], not an endpoint function,
470    /// it has no notion of the state this router has. If you want to create a sub-router
471    /// that shares the same state this router has, use [`Router::with_sub_router_make_fn`] instead.
472    #[must_use]
473    #[inline]
474    pub fn with_endpoint_service<I, T>(mut self, prefix: impl AsRef<str>, service: I) -> Self
475    where
476        I: IntoEndpointService<T>,
477        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
478    {
479        self.set_endpoint_service(prefix, service);
480        self
481    }
482
483    /// Register a nested endpoint service under a prefix (path).
484    ///
485    /// The prefix is used to match the request path and strip it from the request URI.
486    /// Endpoint layer is applied to this sub-service.
487    ///
488    /// Warning: If a sub-service is a plain [`Service`], not an endpoint function,
489    /// it has no notion of the state this router has. If you want to create a sub-router
490    /// that shares the same state this router has, use [`Router::with_sub_router_make_fn`] instead.
491    #[inline]
492    pub fn set_endpoint_service<I, T>(&mut self, prefix: impl AsRef<str>, service: I) -> &mut Self
493    where
494        I: IntoEndpointService<T>,
495        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
496    {
497        let nested = self.layer.layer(service.into_endpoint_service()).boxed();
498        self.set_sub_service_inner(prefix, nested)
499    }
500
501    /// Register a nested service under a prefix (path).
502    ///
503    /// The prefix is used to match the request path and strip it from the request URI.
504    ///
505    /// Warning: This sub-service has no notion of the state this router has. If you want
506    /// to create a sub-router that shares the same state this router has, use [`Router::with_sub_router_make_fn`] instead.
507    /// Also, the endpoint layer is not applied to it, so its Output / Error must match those of the Router
508    /// If you need its type conversions, consider [`Router::with_endpoint_service`]
509    #[must_use]
510    #[inline]
511    pub fn with_sub_service<S>(mut self, prefix: impl AsRef<str>, service: S) -> Self
512    where
513        S: Service<Request, Output = O, Error = E>,
514    {
515        self.set_sub_service(prefix, service);
516        self
517    }
518
519    /// Register a nested service under a prefix.
520    ///
521    /// The prefix is used to match the request path and strip it from the request URI.
522    ///
523    /// Warning: This sub-service has no notion of the state this router has. If you want
524    /// to create a sub-router that shares the same state this router has, use [`Router::with_sub_router_make_fn`] instead.
525    /// Also, the endpoint layer is not applied to it, so its Output / Error must match those of the Router
526    /// If you need its type conversions, consider [`Router::set_endpoint_service`]
527    #[inline]
528    pub fn set_sub_service<S>(&mut self, prefix: impl AsRef<str>, service: S) -> &mut Self
529    where
530        S: Service<Request, Output = O, Error = E>,
531    {
532        let nested = service.into_endpoint_service().boxed();
533        self.set_sub_service_inner(prefix, nested)
534    }
535
536    fn set_sub_service_inner(
537        &mut self,
538        prefix: impl AsRef<str>,
539        nested: BoxService<Request, O, E>,
540    ) -> &mut Self {
541        let router = self.sub_services.get_or_insert_default();
542        router.insert_prefix_with_opts(
543            prefix.as_ref().trim(),
544            crate::matcher::path::HTTP_PATH_OPTS,
545            SubService { svc: nested },
546        );
547
548        self
549    }
550
551    /// add a route to the router with it's matcher and service.
552    #[inline(always)]
553    #[must_use]
554    pub fn with_match_route<I, T>(
555        mut self,
556        path: impl AsRef<str>,
557        matcher: HttpMatcher<Body>,
558        service: I,
559    ) -> Self
560    where
561        I: IntoEndpointServiceWithState<T, State>,
562        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
563    {
564        self.set_match_route(path, matcher, service);
565        self
566    }
567
568    /// add a route to the router with it's matcher and service.
569    pub fn set_match_route<I, T>(
570        &mut self,
571        path: impl AsRef<str>,
572        matcher: HttpMatcher<Body>,
573        service: I,
574    ) -> &mut Self
575    where
576        I: IntoEndpointServiceWithState<T, State>,
577        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
578    {
579        let service = self
580            .layer
581            .layer(service.into_endpoint_service_with_state(self.state.clone()))
582            .boxed();
583
584        let pattern = compile_pattern(path.as_ref());
585
586        if let Some(entry) = self.routes.iter_mut().find(|e| e.pattern == pattern) {
587            entry.handlers.push((matcher, service));
588        } else {
589            let specificity = specificity_of(&pattern);
590            // keep `routes` most-specific-first; first match wins at lookup
591            let pos = self
592                .routes
593                .partition_point(|e| e.specificity >= specificity);
594            self.routes.insert(
595                pos,
596                RouteEntry {
597                    pattern,
598                    specificity,
599                    handlers: vec![(matcher, service)],
600                },
601            );
602        }
603
604        self
605    }
606
607    /// use the provided service when no route matches the request.
608    #[inline(always)]
609    #[must_use]
610    pub fn with_not_found<I, T>(mut self, service: I) -> Self
611    where
612        I: IntoEndpointServiceWithState<T, State>,
613        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
614    {
615        self.set_not_found(service);
616        self
617    }
618
619    /// use the provided service when no route matches the request.
620    pub fn set_not_found<I, T>(&mut self, service: I) -> &mut Self
621    where
622        I: IntoEndpointServiceWithState<T, State>,
623        L: Layer<I::Service, Service: Service<Request, Output = O, Error = E>>,
624    {
625        self.not_found = Some(
626            self.layer
627                .layer(service.into_endpoint_service_with_state(self.state.clone()))
628                .boxed(),
629        );
630        self
631    }
632}
633
634impl Default for Router {
635    fn default() -> Self {
636        Self::new()
637    }
638}
639
640#[derive(Debug, Clone)]
641pub enum RouterError {
642    /// This one should never fire, if it does something is wrong in uri prefix stripped
643    Internal,
644    MethodNotAllowed(Box<NonEmptySmallVec<7, Method>>),
645    NotFound,
646}
647
648impl fmt::Display for RouterError {
649    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
650        fmt::Debug::fmt(self, f)
651    }
652}
653
654impl Error for RouterError {}
655
656impl IntoResponse for RouterError {
657    fn into_response(self) -> Response {
658        match self {
659            Self::Internal => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
660            Self::MethodNotAllowed(allowed) => (
661                Headers::single(Allow(*allowed)),
662                StatusCode::METHOD_NOT_ALLOWED,
663            )
664                .into_response(),
665            Self::NotFound => StatusCode::NOT_FOUND.into_response(),
666        }
667    }
668}
669
670impl From<Infallible> for RouterError {
671    fn from(infallible: Infallible) -> Self {
672        match infallible {}
673    }
674}
675
676struct SubService<O, E> {
677    svc: BoxService<Request, O, E>,
678}
679
680impl<State, L, O, E> Service<Request> for Router<State, L, O, E>
681where
682    O: Send + 'static,
683    E: Send + From<RouterError> + 'static,
684    L: Send + Sync + 'static,
685    State: Send + Sync + Clone + 'static,
686{
687    type Output = O;
688    type Error = E;
689
690    async fn serve(&self, req: Request) -> Result<Self::Output, Self::Error> {
691        let path = req.uri().path_ref_or_root();
692
693        // Collect allowed methods when a path matches but no method matches.
694        // Initialised here so it is visible after the route scan and after
695        // sub_services, letting sub_services take priority over a 405.
696        let mut allowed_methods: Option<MethodMatcher> = None;
697
698        // most-specific-first: the first matching route owns the path
699        // (method mismatch -> 405, no fall-through to a vaguer route).
700        for entry in self.routes.iter() {
701            let ext = Extensions::new();
702            if !match_pattern(&entry.pattern, Some(&ext), path) {
703                continue;
704            }
705
706            // merge with any existing (nested-router) UriParams
707            let captured = ext.get_ref::<UriParams>().cloned().unwrap_or_default();
708            let params = match req.extensions().get_ref::<UriParams>() {
709                Some(existing) => {
710                    let mut params = existing.clone();
711                    params.extend(captured.iter());
712                    params
713                }
714                None => captured,
715            };
716            req.extensions().insert(params);
717
718            for (matcher, service) in entry.handlers.iter() {
719                let mext = Extensions::new();
720                if matcher.matches(Some(&mext), &req) {
721                    req.extensions().extend(&mext);
722                    return service.serve(req).await;
723                }
724            }
725
726            // Path matched but no method matched — collect for a potential 405.
727            // Do not return yet: a sub_service may still handle this request.
728            for (matcher, _) in entry.handlers.iter() {
729                if let Some(m) = matcher.allowed_methods() {
730                    allowed_methods = Some(allowed_methods.map_or(m, |acc| acc.or_method(m)));
731                }
732            }
733            break;
734        }
735
736        let (mut parts, body) = req.into_parts();
737
738        let sub_match = self.sub_services.as_ref().and_then(|router| {
739            router
740                .match_prefix(parts.uri.path_ref_or_root())
741                .map(|matched| {
742                    let (sub_svc, matched_segment_count, captures) = matched.into_parts();
743                    (
744                        sub_svc,
745                        matched_segment_count,
746                        UriParams::from_captures(&captures),
747                    )
748                })
749        });
750
751        if let Some((sub_svc, matched_segment_count, captured)) = sub_match {
752            let mut modified_uri = parts.uri.clone();
753            if !modified_uri
754                .path_mut()
755                .strip_prefix_segments(matched_segment_count)
756            {
757                tracing::warn!(
758                    "failed to strip {matched_segment_count} matched path segments from Uri (bug??)",
759                );
760                return Err(RouterError::Internal.into());
761            }
762
763            if !captured.is_empty() {
764                let params = match parts.extensions.get_ref::<UriParams>() {
765                    Some(existing) => {
766                        let mut params = existing.clone();
767                        params.extend(captured.iter());
768                        params
769                    }
770                    None => captured,
771                };
772                parts.extensions.insert(params);
773            }
774
775            if !parts.extensions.contains::<OriginalRouterUri>() {
776                parts.extensions.insert(OriginalRouterUri(parts.uri));
777            }
778            parts.uri = modified_uri;
779
780            tracing::trace!(
781                "svc request using sub service of router with {matched_segment_count} matched path segments removed from path; new uri: {}",
782                parts.uri,
783            );
784            let req = Request::from_parts(parts, body);
785            return sub_svc.svc.serve(req).await;
786        }
787
788        // A route matched the path but no registered method matched, and no sub_service
789        // handled the request — return 405 with the Allow header per RFC 7231.
790        if let Some(matcher) = allowed_methods
791            && let Some(methods) = NonEmptySmallVec::collect(matcher.iter())
792        {
793            return Err(RouterError::MethodNotAllowed(Box::new(methods)).into());
794        }
795
796        if let Some(not_found) = &self.not_found {
797            let req = Request::from_parts(parts, body);
798            not_found.serve(req).await
799        } else {
800            Err(RouterError::NotFound.into())
801        }
802    }
803}
804
805#[cfg(test)]
806mod tests {
807    use rama_core::{extensions::ExtensionsRef, service::service_fn};
808    use rama_http_types::{Body, Method, Request, StatusCode, body::util::BodyExt, header};
809
810    use super::*;
811    use crate::{
812        layer::error_handling::ErrorHandlerLayer, matcher::UriParams, service::web::extract::State,
813    };
814
815    fn root_service() -> impl Service<Request, Output = Response, Error = Infallible> {
816        service_fn(|_req| async {
817            Ok(Response::builder()
818                .status(200)
819                .body(Body::from("Hello, World!"))
820                .unwrap())
821        })
822    }
823
824    fn create_user_service() -> impl Service<Request, Output = Response, Error = Infallible> {
825        service_fn(|_req| async {
826            Ok(Response::builder()
827                .status(200)
828                .body(Body::from("Create User"))
829                .unwrap())
830        })
831    }
832
833    fn get_users_service() -> impl Service<Request, Output = Response, Error = Infallible> {
834        service_fn(|_req| async {
835            Ok(Response::builder()
836                .status(200)
837                .body(Body::from("List Users"))
838                .unwrap())
839        })
840    }
841
842    fn get_user_service() -> impl Service<Request, Output = Response, Error = Infallible> {
843        service_fn(|req: Request| async move {
844            let uri_params = req.extensions().get_ref::<UriParams>().unwrap();
845            let id = uri_params.get("user_id").unwrap();
846            Ok(Response::builder()
847                .status(200)
848                .body(Body::from(format!("Get User: {id}")))
849                .unwrap())
850        })
851    }
852
853    fn delete_user_service() -> impl Service<Request, Output = Response, Error = Infallible> {
854        service_fn(|req: Request| async move {
855            let uri_params = req.extensions().get_ref::<UriParams>().unwrap();
856            let id = uri_params.get("user_id").unwrap();
857            Ok(Response::builder()
858                .status(200)
859                .body(Body::from(format!("Delete User: {id}")))
860                .unwrap())
861        })
862    }
863
864    fn serve_assets_service() -> impl Service<Request, Output = Response, Error = Infallible> {
865        service_fn(|req: Request| async move {
866            let uri_params = req.extensions().get_ref::<UriParams>().unwrap();
867            let path = uri_params.get("path").unwrap();
868            Ok(Response::builder()
869                .status(200)
870                .body(Body::from(format!("Serve Assets: /{path}")))
871                .unwrap())
872        })
873    }
874
875    fn not_found_service() -> impl Service<Request, Output = Response, Error = Infallible> {
876        service_fn(|_req| async {
877            Ok(Response::builder()
878                .status(StatusCode::NOT_FOUND)
879                .body(Body::from("Not Found"))
880                .unwrap())
881        })
882    }
883
884    fn get_user_order_service() -> impl Service<Request, Output = Response, Error = Infallible> {
885        service_fn(|req: Request| async move {
886            let uri_params = req.extensions().get_ref::<UriParams>().unwrap();
887            let user_id = uri_params.get("user_id").unwrap();
888            let order_id = uri_params.get("order_id").unwrap();
889            Ok(Response::builder()
890                .status(200)
891                .body(Body::from(format!(
892                    "Get Order: {order_id} for User: {user_id}",
893                )))
894                .unwrap())
895        })
896    }
897
898    // Echoes the request content back, proving the QUERY body (the query) reaches the handler.
899    fn query_echo_service() -> impl Service<Request, Output = Response, Error = Infallible> {
900        service_fn(|req: Request| async move {
901            let body = req.into_body().collect().await.unwrap().to_bytes();
902            Ok(Response::builder()
903                .status(200)
904                .body(Body::from(format!(
905                    "query: {}",
906                    String::from_utf8_lossy(&body)
907                )))
908                .unwrap())
909        })
910    }
911
912    #[tokio::test]
913    async fn test_router() {
914        let cases = vec![
915            (Method::GET, "/", "Hello, World!", StatusCode::OK),
916            (Method::GET, "/users", "List Users", StatusCode::OK),
917            (Method::POST, "/users", "Create User", StatusCode::OK),
918            (Method::GET, "/users/123", "Get User: 123", StatusCode::OK),
919            (
920                Method::DELETE,
921                "/users/123",
922                "Delete User: 123",
923                StatusCode::OK,
924            ),
925            (
926                Method::GET,
927                "/users/123/orders/456",
928                "Get Order: 456 for User: 123",
929                StatusCode::OK,
930            ),
931            (
932                Method::PUT,
933                "/users/123",
934                "",
935                StatusCode::METHOD_NOT_ALLOWED,
936            ),
937            (
938                Method::GET,
939                "/assets/css/style.css",
940                "Serve Assets: /css/style.css",
941                StatusCode::OK,
942            ),
943            (
944                Method::GET,
945                "/not-found",
946                "Not Found",
947                StatusCode::NOT_FOUND,
948            ),
949        ];
950
951        for prefix in ["/", ""] {
952            let router = Router::new()
953                .with_get(prefix, root_service())
954                .with_get(format!("{prefix}users"), get_users_service())
955                .with_post(format!("{prefix}users"), create_user_service())
956                .with_get(format!("{prefix}users/{{user_id}}"), get_user_service())
957                .with_delete(format!("{prefix}users/{{user_id}}"), delete_user_service())
958                .with_get(
959                    format!("{prefix}users/{{user_id}}/orders/{{order_id}}"),
960                    get_user_order_service(),
961                )
962                .with_get(format!("{prefix}assets/{{*path}}"), serve_assets_service())
963                .with_not_found(not_found_service());
964
965            let router = ErrorHandlerLayer::new().layer(router);
966
967            for (method, path, expected_body, expected_status) in cases.iter() {
968                let req = match *method {
969                    Method::GET => Request::get(*path),
970                    Method::POST => Request::post(*path),
971                    Method::PUT => Request::put(*path),
972                    Method::DELETE => Request::delete(*path),
973                    _ => panic!("Unsupported HTTP method"),
974                }
975                .body(Body::empty())
976                .unwrap();
977
978                let res = router.serve(req).await.unwrap();
979                assert_eq!(
980                    res.status(),
981                    *expected_status,
982                    "method: {method} ; path = {path}; prefix = {prefix}"
983                );
984                let body = res.into_body().collect().await.unwrap().to_bytes();
985                assert_eq!(
986                    body, expected_body,
987                    "method: {method} ; path = {path}; prefix = {prefix}"
988                );
989            }
990        }
991    }
992
993    #[tokio::test]
994    async fn test_router_query_method() {
995        let router = Router::new()
996            .with_query("/search", query_echo_service())
997            .with_get("/search", get_users_service())
998            .with_not_found(not_found_service());
999
1000        let router = ErrorHandlerLayer::new().layer(router);
1001
1002        // QUERY with a body is routed to the QUERY handler and the body reaches it.
1003        let req = Request::query("/search")
1004            .header(header::CONTENT_TYPE, "application/sql")
1005            .body(Body::from("SELECT 1"))
1006            .unwrap();
1007        let res = router.serve(req).await.unwrap();
1008        assert_eq!(res.status(), StatusCode::OK);
1009        let body = res.into_body().collect().await.unwrap().to_bytes();
1010        assert_eq!(body, "query: SELECT 1");
1011
1012        // GET on the same path is distinct from QUERY and hits the GET handler.
1013        let req = Request::get("/search").body(Body::empty()).unwrap();
1014        let res = router.serve(req).await.unwrap();
1015        assert_eq!(res.status(), StatusCode::OK);
1016        let body = res.into_body().collect().await.unwrap().to_bytes();
1017        assert_eq!(body, "List Users");
1018
1019        // A method registered on neither route → 405 listing GET and QUERY in the Allow header.
1020        let req = Request::post("/search").body(Body::empty()).unwrap();
1021        let res = router.serve(req).await.unwrap();
1022        assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
1023        assert_eq!(
1024            res.headers()
1025                .get(header::ALLOW)
1026                .expect("Allow header must be present on 405")
1027                .to_str()
1028                .unwrap(),
1029            "GET, QUERY"
1030        );
1031    }
1032
1033    #[tokio::test]
1034    async fn test_router_merges_case_insensitive_pattern_registrations() {
1035        let router = Router::new()
1036            .with_get("/Users/{user_id}", get_user_service())
1037            .with_post("/users/{user_id}", create_user_service());
1038
1039        let router = ErrorHandlerLayer::new().layer(router);
1040
1041        let req = Request::post("/USERS/123").body(Body::empty()).unwrap();
1042        let res = router.serve(req).await.unwrap();
1043        assert_eq!(res.status(), StatusCode::OK);
1044        let body = res.into_body().collect().await.unwrap().to_bytes();
1045        assert_eq!(body, "Create User");
1046
1047        let req = Request::put("/users/123").body(Body::empty()).unwrap();
1048        let res = router.serve(req).await.unwrap();
1049        assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
1050        assert_eq!(
1051            res.headers().get(header::ALLOW).unwrap().to_str().unwrap(),
1052            "GET, POST"
1053        );
1054    }
1055
1056    #[tokio::test]
1057    async fn test_router_method_not_allowed() {
1058        let router = Router::new()
1059            .with_get("/users", get_users_service())
1060            .with_post("/users", create_user_service())
1061            .with_get("/users/{user_id}", get_user_service())
1062            .with_delete("/users/{user_id}", delete_user_service())
1063            .with_not_found(not_found_service());
1064
1065        let router = ErrorHandlerLayer::new().layer(router);
1066
1067        // PUT /users/123 → 405: verify status, Allow header, and empty body in one shot
1068        let req = Request::put("/users/123").body(Body::empty()).unwrap();
1069        let res = router.serve(req).await.unwrap();
1070        assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
1071        assert_eq!(
1072            res.headers()
1073                .get(header::ALLOW)
1074                .expect("Allow header must be present on 405")
1075                .to_str()
1076                .unwrap(),
1077            "DELETE, GET"
1078        );
1079        let body = res.into_body().collect().await.unwrap().to_bytes();
1080        assert!(
1081            body.is_empty(),
1082            "405 body must be empty, not from not_found service"
1083        );
1084
1085        // DELETE /users → 405 with a different Allow set (GET, POST)
1086        let req = Request::delete("/users").body(Body::empty()).unwrap();
1087        let res = router.serve(req).await.unwrap();
1088        assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
1089        assert_eq!(
1090            res.headers().get(header::ALLOW).unwrap().to_str().unwrap(),
1091            "GET, POST"
1092        );
1093
1094        // Unknown path → 404, no Allow header (not_found service body present)
1095        let req = Request::get("/nonexistent").body(Body::empty()).unwrap();
1096        let res = router.serve(req).await.unwrap();
1097        assert_eq!(res.status(), StatusCode::NOT_FOUND);
1098        assert!(
1099            res.headers().get(header::ALLOW).is_none(),
1100            "404 must not carry Allow header"
1101        );
1102    }
1103
1104    #[tokio::test]
1105    async fn test_router_method_not_allowed_no_not_found_service() {
1106        // Verify 405 fires correctly when no custom not_found service is registered.
1107        let router = Router::new()
1108            .with_get("/users/{user_id}", get_user_service())
1109            .with_delete("/users/{user_id}", delete_user_service());
1110
1111        let router = ErrorHandlerLayer::new().layer(router);
1112
1113        let req = Request::put("/users/123").body(Body::empty()).unwrap();
1114        let res = router.serve(req).await.unwrap();
1115        assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
1116        assert_eq!(
1117            res.headers().get(header::ALLOW).unwrap().to_str().unwrap(),
1118            "DELETE, GET"
1119        );
1120
1121        // Unknown path without not_found service → plain 404, no Allow header
1122        let req = Request::get("/nonexistent").body(Body::empty()).unwrap();
1123        let res = router.serve(req).await.unwrap();
1124        assert_eq!(res.status(), StatusCode::NOT_FOUND);
1125        assert!(res.headers().get(header::ALLOW).is_none());
1126    }
1127
1128    #[test]
1129    fn specificity_reflects_segment_kinds() {
1130        let spec = |p: &str| {
1131            specificity_of(&compile_pattern(p))
1132                .into_iter()
1133                .map(|rank| rank.kind)
1134                .collect::<Vec<_>>()
1135        };
1136        assert_eq!(spec("/a/b/c"), vec![2, 2, 2]); // all literal
1137        assert_eq!(spec("/users/{id}"), vec![2, 1]); // literal + capture
1138        assert_eq!(spec("/files/{}.json"), vec![2, 1]); // literal + affixed wildcard
1139        assert_eq!(spec("/assets/{*path}"), vec![2, 0]); // literal + catch-all
1140        // An invalid catch-all body is a literal in the matcher, so it ranks as
1141        // a literal here too — the router no longer guesses, so no drift.
1142        assert_eq!(spec("/api/{*bad name}"), vec![2, 2]);
1143    }
1144
1145    #[test]
1146    fn specificity_breaks_dynamic_ties_with_literal_weight() {
1147        let plain = specificity_of(&compile_pattern("/files/{name}"));
1148        let json = specificity_of(&compile_pattern("/files/{name}.json"));
1149        let wildcard_json = specificity_of(&compile_pattern("/files/{}.json"));
1150
1151        assert!(json > plain);
1152        assert!(wildcard_json > plain);
1153    }
1154
1155    #[tokio::test]
1156    async fn test_router_capture_beats_catch_all_either_order() {
1157        fn name_svc() -> impl Service<Request, Output = Response, Error = Infallible> {
1158            service_fn(|req: Request| async move {
1159                let p = req.extensions().get_ref::<UriParams>().unwrap();
1160                Ok(Response::builder()
1161                    .status(200)
1162                    .body(Body::from(format!("name:{}", p.get("name").unwrap())))
1163                    .unwrap())
1164            })
1165        }
1166        fn rest_svc() -> impl Service<Request, Output = Response, Error = Infallible> {
1167            service_fn(|req: Request| async move {
1168                let p = req.extensions().get_ref::<UriParams>().unwrap();
1169                Ok(Response::builder()
1170                    .status(200)
1171                    .body(Body::from(format!("rest:{}", p.get("rest").unwrap())))
1172                    .unwrap())
1173            })
1174        }
1175
1176        // `{name}` (rank 1) must win over `{*rest}` (rank 0) on a single-segment
1177        // path, independent of registration order.
1178        let routers = [
1179            Router::new()
1180                .with_get("/files/{name}", name_svc())
1181                .with_get("/files/{*rest}", rest_svc()),
1182            Router::new()
1183                .with_get("/files/{*rest}", rest_svc())
1184                .with_get("/files/{name}", name_svc()),
1185        ];
1186        for router in routers {
1187            let router = ErrorHandlerLayer::new().layer(router);
1188            let req = Request::get("/files/x").body(Body::empty()).unwrap();
1189            let res = router.serve(req).await.unwrap();
1190            let body = res.into_body().collect().await.unwrap().to_bytes();
1191            assert_eq!(body, "name:x");
1192        }
1193
1194        // A multi-segment path no longer fits `{name}` and falls to the catch-all.
1195        let router = ErrorHandlerLayer::new().layer(
1196            Router::new()
1197                .with_get("/files/{name}", name_svc())
1198                .with_get("/files/{*rest}", rest_svc()),
1199        );
1200        let req = Request::get("/files/a/b").body(Body::empty()).unwrap();
1201        let res = router.serve(req).await.unwrap();
1202        let body = res.into_body().collect().await.unwrap().to_bytes();
1203        assert_eq!(body, "rest:a/b");
1204    }
1205
1206    #[tokio::test]
1207    async fn test_router_affixed_dynamic_beats_plain_dynamic_either_order() {
1208        fn plain_svc() -> impl Service<Request, Output = Response, Error = Infallible> {
1209            service_fn(|req: Request| async move {
1210                let p = req.extensions().get_ref::<UriParams>().unwrap();
1211                Ok(Response::builder()
1212                    .status(200)
1213                    .body(Body::from(format!("plain:{}", p.get("name").unwrap())))
1214                    .unwrap())
1215            })
1216        }
1217        fn json_svc() -> impl Service<Request, Output = Response, Error = Infallible> {
1218            service_fn(|req: Request| async move {
1219                let p = req.extensions().get_ref::<UriParams>().unwrap();
1220                Ok(Response::builder()
1221                    .status(200)
1222                    .body(Body::from(format!("json:{}", p.get("name").unwrap())))
1223                    .unwrap())
1224            })
1225        }
1226
1227        let routers = [
1228            Router::new()
1229                .with_get("/files/{name}", plain_svc())
1230                .with_get("/files/{name}.json", json_svc()),
1231            Router::new()
1232                .with_get("/files/{name}.json", json_svc())
1233                .with_get("/files/{name}", plain_svc()),
1234        ];
1235        for router in routers {
1236            let router = ErrorHandlerLayer::new().layer(router);
1237            let req = Request::get("/files/readme.json")
1238                .body(Body::empty())
1239                .unwrap();
1240            let res = router.serve(req).await.unwrap();
1241            let body = res.into_body().collect().await.unwrap().to_bytes();
1242            assert_eq!(body, "json:readme");
1243        }
1244    }
1245
1246    #[tokio::test]
1247    async fn test_router_equal_specificity_preserves_registration_order() {
1248        fn svc(
1249            label: &'static str,
1250        ) -> impl Service<Request, Output = Response, Error = Infallible> {
1251            service_fn(move |_req: Request| async move {
1252                Ok(Response::builder()
1253                    .status(200)
1254                    .body(Body::from(label))
1255                    .unwrap())
1256            })
1257        }
1258
1259        let router = ErrorHandlerLayer::new().layer(
1260            Router::new()
1261                .with_get("/files/{a}", svc("first"))
1262                .with_get("/files/{b}", svc("second")),
1263        );
1264        let req = Request::get("/files/readme").body(Body::empty()).unwrap();
1265        let res = router.serve(req).await.unwrap();
1266        let body = res.into_body().collect().await.unwrap().to_bytes();
1267        assert_eq!(body, "first");
1268    }
1269
1270    #[tokio::test]
1271    async fn test_router_anonymous_glob_surfaces_via_uri_params() {
1272        fn glob_svc() -> impl Service<Request, Output = Response, Error = Infallible> {
1273            service_fn(|req: Request| async move {
1274                let p = req.extensions().get_ref::<UriParams>().unwrap();
1275                // anon `{*}` is the glob, not a named param
1276                assert!(p.get("rest").is_none());
1277                Ok(Response::builder()
1278                    .status(200)
1279                    .body(Body::from(format!("glob:{}", p.glob().unwrap())))
1280                    .unwrap())
1281            })
1282        }
1283        let router =
1284            ErrorHandlerLayer::new().layer(Router::new().with_get("/assets/{*}", glob_svc()));
1285        let req = Request::get("/assets/css/app.css")
1286            .body(Body::empty())
1287            .unwrap();
1288        let res = router.serve(req).await.unwrap();
1289        let body = res.into_body().collect().await.unwrap().to_bytes();
1290        // glob() is path-style (leading `/`), unlike a named catch-all param.
1291        assert_eq!(body, "glob:/css/app.css");
1292    }
1293
1294    #[tokio::test]
1295    async fn test_router_invalid_catch_all_mount_is_literal() {
1296        // RAMA-PR1027-001: `{*bad name}` is an invalid catch-all body, so the
1297        // matcher treats it as a literal segment. The mount must therefore NOT
1298        // collapse to `/api`; `/api/users` must not reach the nested service.
1299        let app = Router::new()
1300            .with_sub_service(
1301                "/api/{*bad name}",
1302                Router::new().with_get("/", root_service()),
1303            )
1304            .with_not_found(not_found_service());
1305        let app = ErrorHandlerLayer::new().layer(app);
1306
1307        let req = Request::get("/api/users").body(Body::empty()).unwrap();
1308        let res = app.serve(req).await.unwrap();
1309        assert_eq!(
1310            res.status(),
1311            StatusCode::NOT_FOUND,
1312            "invalid catch-all must not mount the sub-service at /api"
1313        );
1314    }
1315
1316    #[tokio::test]
1317    async fn test_sub_service_mount_respects_segment_boundaries() {
1318        for prefix in ["/api", "/api/{id}"] {
1319            let app = Router::new()
1320                .with_sub_service(prefix, Router::new().with_get("/", root_service()))
1321                .with_not_found(not_found_service());
1322            let app = ErrorHandlerLayer::new().layer(app);
1323
1324            let req = Request::get("/apix/123").body(Body::empty()).unwrap();
1325            let res = app.serve(req).await.unwrap();
1326            assert_eq!(
1327                res.status(),
1328                StatusCode::NOT_FOUND,
1329                "mount prefix {prefix:?} must not match inside a path segment",
1330            );
1331        }
1332    }
1333
1334    #[tokio::test]
1335    #[tracing_test::traced_test]
1336    async fn test_router_nest() {
1337        let cases = [
1338            (Method::GET, "/", "Hello, World!", StatusCode::OK),
1339            (Method::GET, "/api/users", "List Users", StatusCode::OK),
1340            (Method::POST, "/api/users", "Create User", StatusCode::OK),
1341            (
1342                Method::DELETE,
1343                "/api/users/123",
1344                "Delete User: 123",
1345                StatusCode::OK,
1346            ),
1347            (
1348                Method::GET,
1349                "/api/users/123",
1350                "Get User: 123",
1351                StatusCode::OK,
1352            ),
1353            (
1354                Method::GET,
1355                "/api/users/123/orders/456",
1356                "Get Order: 456 for User: 123",
1357                StatusCode::OK,
1358            ),
1359            (Method::GET, "/api/state", "state", StatusCode::OK),
1360            (Method::GET, "/api/users/123/state", "state", StatusCode::OK),
1361            // to test case insensitive :)
1362            (Method::GET, "/Api/USERS/123/State", "state", StatusCode::OK),
1363        ];
1364
1365        let state = "state".to_owned();
1366
1367        for prefix in ["/", ""] {
1368            let api_router = Router::new_with_state(state.clone())
1369                .with_get(format!("{prefix}users"), get_users_service())
1370                .with_get(
1371                    format!("{prefix}state"),
1372                    async |State(state): State<String>| state,
1373                )
1374                .with_post(format!("{prefix}users"), create_user_service())
1375                .with_delete(format!("{prefix}users/{{user_id}}"), delete_user_service())
1376                .with_sub_router_make_fn(
1377                    format!("{prefix}users/{{user_id}}/{{*}}"), // glob should be dropped by nester
1378                    |router| {
1379                        router
1380                            .with_get(prefix, get_user_service())
1381                            .with_get(
1382                                format!("{prefix}orders/{{order_id}}"),
1383                                get_user_order_service(),
1384                            )
1385                            .with_get(
1386                                format!("{prefix}/state"),
1387                                async |State(state): State<String>| state,
1388                            )
1389                    },
1390                );
1391
1392            let app = Router::new()
1393                .with_sub_service(format!("{prefix}api"), api_router)
1394                .with_get(prefix, root_service());
1395
1396            let app = ErrorHandlerLayer::new().layer(app);
1397
1398            for (method, path, expected_body, expected_status) in cases.iter() {
1399                let req = match *method {
1400                    Method::GET => Request::get(*path),
1401                    Method::POST => Request::post(*path),
1402                    Method::DELETE => Request::delete(*path),
1403                    _ => panic!("Unsupported HTTP method"),
1404                }
1405                .body(Body::empty())
1406                .unwrap();
1407
1408                let res = app.serve(req).await.unwrap();
1409                assert_eq!(
1410                    res.status(),
1411                    *expected_status,
1412                    "method: {method} ; path = {path}; prefix = {prefix}"
1413                );
1414                let body = res.into_body().collect().await.unwrap().to_bytes();
1415                assert_eq!(
1416                    body, expected_body,
1417                    "method: {method} ; path = {path}; prefix = {prefix}"
1418                );
1419            }
1420
1421            // PUT /api/users/123 → the outer router has no direct route for this path,
1422            // but the api_router has DELETE /users/{user_id} registered directly.
1423            // However the sub-router for users/{user_id}/* takes priority (deferred 405),
1424            // and that sub-router has GET / — so the final 405 reflects Allow: GET.
1425            let req = Request::put("/api/users/123").body(Body::empty()).unwrap();
1426            let res = app.serve(req).await.unwrap();
1427            assert_eq!(
1428                res.status(),
1429                StatusCode::METHOD_NOT_ALLOWED,
1430                "nested router: PUT /api/users/123 must be 405; prefix = {prefix}"
1431            );
1432            assert_eq!(
1433                res.headers().get(header::ALLOW).unwrap().to_str().unwrap(),
1434                "GET",
1435                "nested router: Allow header reflects sub-router's registered methods; prefix = {prefix}"
1436            );
1437        }
1438    }
1439}