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
17pub type LayerFuture<'a> = Pin<Box<dyn Future<Output = Result<Response>> + Send + 'a>>;
20
21pub trait Layer: Send + Sync + 'static {
70 fn path(&self) -> Option<&Path>;
73
74 fn context(&self) -> ContextMap {
79 ContextMap::new()
80 }
81
82 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
103pub type LayerHandlerFn = for<'a> fn(cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a>;
105
106#[derive(Debug, Clone)]
111pub struct LayerFn {
112 path: Option<Cow<'static, Path>>,
115 handle: LayerHandlerFn,
117 context: ContextMap,
119}
120
121impl LayerFn {
122 #[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 #[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
165pub(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 matching.sort_by_key(|(layer, _)| layer.path().map_or(0, |path| path.len() + 1));
185 matching
186}
187
188#[derive(Clone, Copy)]
196pub(crate) enum Terminal<'a> {
197 Route(&'a dyn Route),
199 MethodNotAllowed(&'a Endpoint),
202 NotFound,
204}
205
206pub struct Next<'a> {
212 layers: &'a [Arc<dyn Layer>],
215 terminal: Terminal<'a>,
217}
218
219impl<'a> Next<'a> {
220 pub(crate) fn new(layers: &'a [Arc<dyn Layer>], terminal: Terminal<'a>) -> Self {
225 Self { layers, terminal }
226 }
227
228 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 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 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 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 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 fn body_bytes(response: Response) -> Bytes {
314 let (_, body) = response.into_parts();
315 block_on(to_bytes(body, usize::MAX)).unwrap()
316 }
317
318 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 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 #[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 #[test]
366 fn for_path_orders_prefix_layers_least_to_most_specific() {
367 let layers = [layer_at("/"), layer_at("/users"), layer_at("/posts")];
368 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 assert_selects(&layers, "/users", &[None, Some("/")]);
379
380 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 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 assert_selects(&layers, "/users/{id}/posts", &[Some("/users/{id}")]);
415 }
416
417 #[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 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 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 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}