Skip to main content

topcoat_router/
layer.rs

1mod strip_prefix;
2
3use std::{any::Any, borrow::Cow, pin::Pin, sync::Arc};
4
5pub use strip_prefix::*;
6use topcoat_core::{
7    context::{ContextMap, Cx},
8    error::Result,
9};
10
11use crate::{
12    Body, Endpoint, IntoPath, Path, Route,
13    error::{method_not_allowed, not_found},
14    response::Response,
15};
16
17/// The future returned by [`Layer::handle`] and [`Next::run`]: a boxed, `Send`
18/// future borrowing the chain and the request context.
19pub type LayerFuture<'a> = Pin<Box<dyn Future<Output = Result<Response>> + Send + 'a>>;
20
21/// A request-processing layer that wraps the routes nested under its path,
22/// similar to a tower middleware.
23///
24/// A layer with a path wraps handlers whose registered path starts with it,
25/// compared segment by segment. A layer without a path wraps every request.
26/// If no route matches, [`Next::run`] returns the not-found or
27/// method-not-allowed error to pathless layers.
28///
29/// Call [`Next::run`] to run the remaining layers and handler, then inspect
30/// or modify their response. Pass a child context from [`Cx::with`] to make
31/// a value available to them. Use
32/// [`response_headers`](crate::response::response_headers) for headers that
33/// must also appear on error responses, which are built after the error
34/// leaves the layer chain.
35///
36/// When several layers match a route they nest from least-specific (outermost)
37/// to most-specific (innermost), like layouts; a layer without a path runs
38/// outside every layer with one.
39///
40/// Register layers with [`RouterBuilder::layer`](crate::RouterBuilder::layer).
41///
42/// # Examples
43///
44/// ```rust
45/// use std::borrow::Cow;
46///
47/// use topcoat::{
48///     context::Cx,
49///     router::{Body, Layer, LayerFuture, Next, Path},
50/// };
51///
52/// struct Timing;
53///
54/// impl Layer for Timing {
55///     fn path(&self) -> Option<&Path> {
56///         None
57///     }
58///
59///     fn handle<'a>(&'a self, cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
60///         Box::pin(async move {
61///             let start = std::time::Instant::now();
62///             let response = next.run(cx, body).await?;
63///             println!("handled in {:?}", start.elapsed());
64///             Ok(response)
65///         })
66///     }
67/// }
68/// ```
69pub trait Layer: Send + Sync + 'static {
70    /// The URL path prefix whose matched routes this layer wraps, or `None`
71    /// to wrap every request.
72    fn path(&self) -> Option<&Path>;
73
74    /// The values attached to this layer, such as defaults for the routes it
75    /// wraps.
76    ///
77    /// Defaults to an empty map.
78    fn context(&self) -> ContextMap {
79        ContextMap::new()
80    }
81
82    /// Handles a request, calling `next` to continue down the chain.
83    fn handle<'a>(&'a self, cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a>;
84}
85
86impl<L: Layer + ?Sized> Layer for &'static L {
87    fn path(&self) -> Option<&Path> {
88        (**self).path()
89    }
90
91    fn context(&self) -> ContextMap {
92        (**self).context()
93    }
94
95    fn handle<'a>(&'a self, cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
96        (**self).handle(cx, body, next)
97    }
98}
99
100#[cfg(feature = "discover")]
101inventory::collect!(&'static dyn Layer);
102
103/// The handler function backing a [`LayerFn`].
104pub type LayerHandlerFn = for<'a> fn(cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a>;
105
106/// A [`Layer`] backed by a plain handler function.
107///
108/// Turns a function into a layer without implementing [`Layer`] on a struct,
109/// pairing it with the path prefix it applies to.
110#[derive(Debug, Clone)]
111pub struct LayerFn {
112    /// The URL path prefix whose matched routes this layer wraps, or `None`
113    /// to wrap every request.
114    path: Option<Cow<'static, Path>>,
115    /// The handler function that wraps the inner chain.
116    handle: LayerHandlerFn,
117    /// The values attached to this layer.
118    context: ContextMap,
119}
120
121impl LayerFn {
122    /// Creates a new layer from a handler function: pass a path prefix to
123    /// wrap the matched routes under it, or `None` to wrap every request.
124    ///
125    /// # Panics
126    ///
127    /// Panics if the path is a string that is not a well-formed route path.
128    #[track_caller]
129    pub fn new(path: Option<impl IntoPath>, handle: LayerHandlerFn) -> Self {
130        Self {
131            path: path.map(IntoPath::into_path),
132            handle,
133            context: ContextMap::new(),
134        }
135    }
136
137    /// Adds `value` to the context of this layer.
138    ///
139    /// The value shadows an earlier value of the same type. Read it during
140    /// a request with [`context`](topcoat_core::context::context).
141    #[must_use]
142    pub fn with<T>(mut self, value: T) -> Self
143    where
144        T: Any + Send + Sync,
145    {
146        self.context.insert(value);
147        self
148    }
149}
150
151impl Layer for LayerFn {
152    fn path(&self) -> Option<&Path> {
153        self.path.as_deref()
154    }
155
156    fn context(&self) -> ContextMap {
157        self.context.clone()
158    }
159
160    fn handle<'a>(&'a self, cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
161        (self.handle)(cx, body, next)
162    }
163}
164
165/// Selects the layers in `layers` wrapping a route at `path`: those without a
166/// path and those whose path is a prefix of `path`, ordered least- to
167/// most-specific so the outermost layer runs first. A layer without a path is
168/// the least specific of all; among layers that share a path, the later one
169/// in `layers` runs first.
170///
171/// Each layer is paired with its context, so the context is built once and
172/// shared by every route the layer wraps.
173pub(crate) fn layers_for_path<'a>(
174    layers: &'a [(Arc<dyn Layer>, ContextMap)],
175    path: &Path,
176) -> Vec<&'a (Arc<dyn Layer>, ContextMap)> {
177    let mut matching: Vec<_> = layers
178        .iter()
179        .filter(|(layer, _)| layer.path().is_none_or(|prefix| path.starts_with(prefix)))
180        .rev()
181        .collect();
182    // The root path's backing string is empty, so its length is offset to
183    // keep a pathless layer strictly less specific than a layer at `/`.
184    matching.sort_by_key(|(layer, _)| layer.path().map_or(0, |path| path.len() + 1));
185    matching
186}
187
188/// What a [`Next`] chain runs once its layers are exhausted.
189///
190/// A matched route runs inside the layer stack selected for its own path,
191/// group segments included. A request that matched no route runs inside the
192/// layers without a path only, and its chain resolves to the not-found or
193/// method-not-allowed error, so a layer sees a matched route handler's
194/// result, or the error, uniformly as the `Result` returned by [`Next::run`].
195#[derive(Clone, Copy)]
196pub(crate) enum Terminal<'a> {
197    /// A matched route handles the request.
198    Route(&'a dyn Route),
199    /// The path matched but the method did not; the chain resolves to a
200    /// method-not-allowed error listing the endpoint's supported methods.
201    MethodNotAllowed(&'a Endpoint),
202    /// The path matched no endpoint; the chain resolves to a not-found error.
203    NotFound,
204}
205
206/// The continuation of a [`Layer`] chain: the remaining layers followed by the
207/// chain's terminal handler.
208///
209/// Passed as the `next` argument to [`Layer::handle`]. Call [`run`](Self::run)
210/// to invoke the next layer, or the terminal once the layers are exhausted.
211pub struct Next<'a> {
212    /// The layers wrapping this request, ordered from least- to most-specific
213    /// so the outermost layer runs first.
214    layers: &'a [Arc<dyn Layer>],
215    /// What runs once the layers are exhausted.
216    terminal: Terminal<'a>,
217}
218
219impl<'a> Next<'a> {
220    /// Creates a chain that runs `layers` (in order), then `terminal`.
221    ///
222    /// `layers` must be ordered from least- to most-specific (ascending path
223    /// length), so the outermost layer runs first.
224    pub(crate) fn new(layers: &'a [Arc<dyn Layer>], terminal: Terminal<'a>) -> Self {
225        Self { layers, terminal }
226    }
227
228    /// Runs the next layer in the chain, or the terminal handler once no layers
229    /// remain.
230    pub fn run(self, cx: &'a Cx, body: Body) -> LayerFuture<'a> {
231        match self.layers.split_first() {
232            Some((layer, rest)) => layer.handle(
233                cx,
234                body,
235                Next {
236                    layers: rest,
237                    ..self
238                },
239            ),
240            None => match self.terminal {
241                Terminal::Route(route) => route.handle(cx, body),
242                Terminal::MethodNotAllowed(endpoint) => {
243                    let error = method_not_allowed(endpoint.methods().cloned());
244                    Box::pin(async move { Err(error.into()) })
245                }
246                Terminal::NotFound => Box::pin(async { Err(not_found().into()) }),
247            },
248        }
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use std::{
255        future::Future,
256        sync::{Arc, Mutex},
257    };
258
259    use http::StatusCode;
260    use topcoat_core::context::{Cx, context};
261
262    use super::*;
263    use crate::{
264        Method, RouteFn, RouteFuture, RouteIndex, error::respond, request::Bytes,
265        response::IntoResponse, to_bytes,
266    };
267
268    // -- Test helpers --
269
270    fn block_on<F: Future>(future: F) -> F::Output {
271        tokio::runtime::Builder::new_current_thread()
272            .build()
273            .unwrap()
274            .block_on(future)
275    }
276
277    fn path(s: &'static str) -> Cow<'static, Path> {
278        Cow::Borrowed(Path::new(s))
279    }
280
281    /// A layer whose path is all a test cares about, paired with an empty
282    /// context; its handler just forwards to the rest of the chain and never
283    /// runs in the selection tests.
284    fn layer_at(p: &'static str) -> (Arc<dyn Layer>, ContextMap) {
285        let layer: Arc<dyn Layer> = Arc::new(LayerFn::new(Some(path(p)), noop_layer));
286        (layer, ContextMap::new())
287    }
288
289    /// A layer without a path, wrapping every request in the selection tests.
290    fn layer_always() -> (Arc<dyn Layer>, ContextMap) {
291        let layer: Arc<dyn Layer> = Arc::new(LayerFn::new(None::<&Path>, noop_layer));
292        (layer, ContextMap::new())
293    }
294
295    /// Asserts that `layers_for_path` selects the layers at the `expected`
296    /// paths (`None` for a layer without one), in order.
297    fn assert_selects(
298        layers: &[(Arc<dyn Layer>, ContextMap)],
299        p: &'static str,
300        expected: &[Option<&'static str>],
301    ) {
302        let selected = layers_for_path(layers, Path::new(p));
303        let paths: Vec<Option<&Path>> = selected.iter().map(|(layer, _)| layer.path()).collect();
304        let expected: Vec<Option<&Path>> = expected.iter().map(|e| e.map(Path::new)).collect();
305        assert_eq!(paths, expected);
306    }
307
308    fn noop_layer<'a>(cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
309        next.run(cx, body)
310    }
311
312    /// Reads a response body to completion.
313    fn body_bytes(response: Response) -> Bytes {
314        let (_, body) = response.into_parts();
315        block_on(to_bytes(body, usize::MAX)).unwrap()
316    }
317
318    /// A shared log of the labels layers and routes record as they run, so a
319    /// test can observe the order the chain executes in.
320    type Trace = Mutex<Vec<&'static str>>;
321
322    fn record_a<'a>(cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
323        Box::pin(async move {
324            context::<Arc<Trace>>(cx).lock().unwrap().push("a");
325            next.run(cx, body).await
326        })
327    }
328
329    fn record_b<'a>(cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
330        Box::pin(async move {
331            context::<Arc<Trace>>(cx).lock().unwrap().push("b");
332            next.run(cx, body).await
333        })
334    }
335
336    /// A layer that answers the request itself, without invoking `next`.
337    fn short_circuit<'a>(cx: &'a Cx, _body: Body, _next: Next<'a>) -> LayerFuture<'a> {
338        Box::pin(async move { "short".into_response(cx) })
339    }
340
341    fn say_route(cx: &Cx, _body: Body) -> RouteFuture<'_> {
342        Box::pin(async move { "route".into_response(cx) })
343    }
344
345    fn record_route(cx: &Cx, _body: Body) -> RouteFuture<'_> {
346        Box::pin(async move {
347            context::<Arc<Trace>>(cx).lock().unwrap().push("route");
348            "route".into_response(cx)
349        })
350    }
351
352    // -- LayerFn --
353
354    #[test]
355    fn layer_fn_exposes_its_path() {
356        let layer = LayerFn::new(Some(path("/admin")), noop_layer);
357        assert_eq!(layer.path(), Some(Path::new("/admin")));
358
359        let layer = LayerFn::new(None::<&Path>, noop_layer);
360        assert_eq!(layer.path(), None);
361    }
362
363    // -- layers_for_path --
364
365    #[test]
366    fn for_path_orders_prefix_layers_least_to_most_specific() {
367        let layers = [layer_at("/"), layer_at("/users"), layer_at("/posts")];
368        // The route at /users/{id} is wrapped by the root and /users layers, in
369        // that order; the /posts layer does not prefix it.
370        assert_selects(&layers, "/users/{id}", &[Some("/"), Some("/users")]);
371    }
372
373    #[test]
374    fn for_path_puts_pathless_layers_outermost() {
375        let layers = [layer_at("/"), layer_always()];
376        // A layer without a path wraps every route, outside the layers whose
377        // paths match it.
378        assert_selects(&layers, "/users", &[None, Some("/")]);
379
380        // The same holds with the registration order reversed: a pathless
381        // layer stays outside a root-path layer regardless of order.
382        let layers = [layer_always(), layer_at("/")];
383        assert_selects(&layers, "/users", &[None, Some("/")]);
384    }
385
386    #[test]
387    fn for_path_runs_the_later_of_a_shared_path_first() {
388        let layers = [layer_at("/admin"), layer_at("/admin")];
389        let selected = layers_for_path(&layers, Path::new("/admin/users"));
390        assert_eq!(selected.len(), 2);
391        assert!(Arc::ptr_eq(&selected[0].0, &layers[1].0));
392        assert!(Arc::ptr_eq(&selected[1].0, &layers[0].0));
393    }
394
395    #[test]
396    fn for_path_rejects_partial_segments() {
397        let layers = [layer_at("/admin")];
398        assert!(layers_for_path(&layers, Path::new("/administrator")).is_empty());
399    }
400
401    #[test]
402    fn for_path_includes_group_segments() {
403        let layers = [layer_at("/(auth)"), layer_at("/dashboard")];
404        // Groups are part of the logical path: the layer inside `(auth)` wraps
405        // the endpoint, while the URL-lookalike `/dashboard` layer does not.
406        assert_selects(&layers, "/(auth)/dashboard", &[Some("/(auth)")]);
407    }
408
409    #[test]
410    fn for_path_distinguishes_param_names() {
411        let layers = [layer_at("/users/{id}"), layer_at("/users/{user_id}")];
412        // Prefix matching compares segments, so `{id}` only wraps endpoints
413        // spelled with the same parameter name.
414        assert_selects(&layers, "/users/{id}/posts", &[Some("/users/{id}")]);
415    }
416
417    // -- Next --
418
419    #[test]
420    fn run_invokes_the_route_terminal_when_no_layers_remain() {
421        let route = RouteFn::new(Method::GET, path("/x"), say_route);
422        let cx = Cx::new();
423
424        let next = Next::new(&[], Terminal::Route(&route));
425        let result = block_on(next.run(&cx, Body::empty()));
426        let response = respond(&cx, result);
427
428        assert_eq!(response.status(), StatusCode::OK);
429        assert_eq!(&body_bytes(response)[..], b"route");
430    }
431
432    #[test]
433    fn run_resolves_the_method_not_allowed_terminal() {
434        let mut endpoint = Endpoint::new(&path("/x"));
435        endpoint.insert(Method::GET, RouteIndex::new(0));
436        endpoint.insert(Method::POST, RouteIndex::new(1));
437        let cx = Cx::new();
438
439        let next = Next::new(&[], Terminal::MethodNotAllowed(&endpoint));
440        let result = block_on(next.run(&cx, Body::empty()));
441        let response = respond(&cx, result);
442
443        assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED);
444        // The `Allow` header is built from the endpoint's supported methods.
445        let allow = response
446            .headers()
447            .get(http::header::ALLOW)
448            .unwrap()
449            .to_str()
450            .unwrap();
451        assert!(allow.contains("GET"), "{allow:?}");
452        assert!(allow.contains("POST"), "{allow:?}");
453    }
454
455    #[test]
456    fn run_walks_layers_in_order_before_the_terminal() {
457        let layers: [Arc<dyn Layer>; 2] = [
458            Arc::new(LayerFn::new(Some(path("/")), record_a)),
459            Arc::new(LayerFn::new(Some(path("/")), record_b)),
460        ];
461        let route = RouteFn::new(Method::GET, path("/x"), record_route);
462
463        let trace: Arc<Trace> = Arc::new(Mutex::new(Vec::new()));
464        let cx = Cx::new().with(trace.clone());
465
466        let next = Next::new(&layers, Terminal::Route(&route));
467        block_on(next.run(&cx, Body::empty())).unwrap();
468
469        // The layers run in slice order, then the terminal route.
470        assert_eq!(*trace.lock().unwrap(), vec!["a", "b", "route"]);
471    }
472
473    #[test]
474    fn run_lets_a_layer_short_circuit_without_calling_next() {
475        let layers: [Arc<dyn Layer>; 1] = [Arc::new(LayerFn::new(Some(path("/")), short_circuit))];
476        // The route would answer "route", but the layer never calls `next.run`.
477        let route = RouteFn::new(Method::GET, path("/x"), say_route);
478        let cx = Cx::new();
479
480        let next = Next::new(&layers, Terminal::Route(&route));
481        let result = block_on(next.run(&cx, Body::empty()));
482        let response = respond(&cx, result);
483
484        assert_eq!(&body_bytes(response)[..], b"short");
485    }
486}