Skip to main content

topcoat_router/
route.rs

1#[cfg(feature = "fs")]
2mod directory;
3
4use std::{
5    any::Any,
6    borrow::Cow,
7    collections::HashMap,
8    num::NonZeroUsize,
9    ops::Index,
10    pin::Pin,
11    sync::{
12        Arc, LazyLock,
13        atomic::{AtomicUsize, Ordering},
14    },
15};
16
17#[cfg(feature = "fs")]
18pub use directory::*;
19use topcoat_core::{
20    context::{ContextMap, Cx},
21    error::Result,
22};
23
24use crate::{
25    Body, Endpoint, EndpointIndex, HrefTarget, IntoPath, Layer, Methods, Next, OwnedMethods, Path,
26    Terminal, response::Response, route, route_endpoint,
27};
28
29/// The future returned by [`Route::handle`]: a boxed, `Send` future borrowing
30/// the route and its request context.
31pub type RouteFuture<'cx> = Pin<Box<dyn Future<Output = Result<Response>> + Send + 'cx>>;
32
33/// The identity of a registered handler.
34///
35/// Ids are drawn from a process-wide counter with [`new`](RouteId::new), so
36/// every handler in an application gets a distinct one.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct RouteId(usize);
39
40impl RouteId {
41    /// Draws the next id from the process-wide counter.
42    ///
43    /// A handler calls this once and keeps the result as its identity.
44    #[must_use]
45    #[allow(clippy::new_without_default)]
46    pub fn new() -> Self {
47        static NEXT: AtomicUsize = AtomicUsize::new(0);
48        Self(NEXT.fetch_add(1, Ordering::Relaxed))
49    }
50}
51
52/// A single routable endpoint: a set of HTTP methods, a URL path, and a
53/// handler.
54///
55/// This is the core primitive a [`Router`](crate::Router) dispatches to.
56/// Register any `Route` with [`RouterBuilder::route`](crate::RouterBuilder::route).
57pub trait Route: Send + Sync + 'static {
58    /// The identity of this route's handler.
59    fn id(&self) -> RouteId;
60
61    /// The HTTP methods this route responds to.
62    fn methods(&self) -> Methods<'_>;
63
64    /// The URL path this route handles.
65    fn path(&self) -> &Path;
66
67    /// The values attached to this route, such as metadata that describes it.
68    ///
69    /// Defaults to an empty map.
70    fn context(&self) -> ContextMap {
71        ContextMap::new()
72    }
73
74    /// Handles a request, producing a response.
75    fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx>;
76
77    /// Returns whether this route handles the current request.
78    ///
79    /// Only the handler is compared, so a route is current for every value its
80    /// path parameters take, whatever the request's query or fragment.
81    ///
82    /// # Panics
83    ///
84    /// Panics if the request matched no route: either its path matched no
85    /// endpoint, or the endpoint holds no route for the request's method.
86    fn is_current(&self, cx: &Cx) -> bool {
87        route(cx).id() == self.id()
88    }
89}
90
91impl<R: Route + ?Sized> Route for &'static R {
92    fn id(&self) -> RouteId {
93        (**self).id()
94    }
95
96    fn methods(&self) -> Methods<'_> {
97        (**self).methods()
98    }
99
100    fn path(&self) -> &Path {
101        (**self).path()
102    }
103
104    fn context(&self) -> ContextMap {
105        (**self).context()
106    }
107
108    fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
109        (**self).handle(cx, body)
110    }
111}
112
113#[cfg(feature = "discover")]
114inventory::collect!(&'static dyn Route);
115
116/// The async handler function backing a [`RouteFn`].
117pub type RouteHandlerFn = for<'cx> fn(cx: &'cx Cx, body: Body) -> RouteFuture<'cx>;
118
119/// A [`Route`] backed by a plain handler function.
120///
121/// Turns a function into a route without implementing [`Route`] on a struct,
122/// pairing it with the methods and path it serves.
123#[derive(Debug, Clone)]
124pub struct RouteFn {
125    /// The identity of this route's handler.
126    id: RouteId,
127    /// The HTTP methods this route responds to.
128    methods: OwnedMethods,
129    /// The URL path this route handles.
130    path: Cow<'static, Path>,
131    /// The handler function that produces the response.
132    handle: RouteHandlerFn,
133    /// The values attached to this route.
134    context: ContextMap,
135}
136
137impl RouteFn {
138    /// Creates a new route with explicit methods, path, and handler function.
139    ///
140    /// The methods are anything convertible into [`OwnedMethods`]: a single
141    /// [`Method`](crate::Method), a `&'static [Method]`, a `Vec<Method>`, or
142    /// [`Methods::Any`] to respond to every method.
143    ///
144    /// ```rust
145    /// use topcoat::{
146    ///     context::Cx,
147    ///     router::{Body, Method, RouteFn, RouteFuture},
148    /// };
149    ///
150    /// fn handler(_cx: &Cx, _body: Body) -> RouteFuture<'_> {
151    ///     Box::pin(async move { unimplemented!() })
152    /// }
153    ///
154    /// let form = RouteFn::new(&[Method::GET, Method::POST], "/form", handler);
155    /// ```
156    ///
157    /// # Panics
158    ///
159    /// Panics if `path` is a string that is not a well-formed route path.
160    #[track_caller]
161    pub fn new(
162        methods: impl Into<OwnedMethods>,
163        path: impl IntoPath,
164        handle: RouteHandlerFn,
165    ) -> Self {
166        Self {
167            id: RouteId::new(),
168            methods: methods.into(),
169            path: path.into_path(),
170            handle,
171            context: ContextMap::new(),
172        }
173    }
174
175    /// Adds `value` to the context of this route.
176    ///
177    /// The value shadows an earlier value of the same type. Read it during
178    /// a request with [`context`](topcoat_core::context::context).
179    #[must_use]
180    pub fn with<T>(mut self, value: T) -> Self
181    where
182        T: Any + Send + Sync,
183    {
184        self.context.insert(value);
185        self
186    }
187}
188
189impl Route for RouteFn {
190    fn id(&self) -> RouteId {
191        self.id
192    }
193
194    fn methods(&self) -> Methods<'_> {
195        self.methods.as_methods()
196    }
197
198    fn path(&self) -> &Path {
199        &self.path
200    }
201
202    fn context(&self) -> ContextMap {
203        self.context.clone()
204    }
205
206    fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
207        (self.handle)(cx, body)
208    }
209}
210
211impl HrefTarget for RouteFn {
212    #[track_caller]
213    fn path<'cx>(&self, cx: &'cx Cx) -> &'cx Path {
214        match route_endpoint(cx, self.id) {
215            Some(endpoint) => endpoint.path(),
216            None => panic!(
217                "route `{}` is not registered on the router serving this request",
218                self.path
219            ),
220        }
221    }
222}
223
224/// The position of a route in a router's [`Routes`] table.
225///
226/// Stored offset by one in a [`NonZeroUsize`] so that `Option<RouteIndex>`
227/// occupies a single word, keeping an endpoint's per-method table dense.
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub(crate) struct RouteIndex(NonZeroUsize);
230
231impl RouteIndex {
232    /// Wraps a route's position in the table.
233    pub(crate) fn new(index: usize) -> Self {
234        Self(NonZeroUsize::new(index.wrapping_add(1)).expect("route index overflow"))
235    }
236
237    /// Returns the wrapped position.
238    pub(crate) fn get(self) -> usize {
239        self.0.get() - 1
240    }
241}
242
243/// A route paired with the layers that wrap it.
244pub(crate) struct RouteWithLayers {
245    /// The route itself.
246    route: Box<dyn Route>,
247    /// The layers wrapping this route, precomputed at build time from the
248    /// route's path (group segments included) and ordered from least- to
249    /// most-specific so the outermost layer runs first.
250    layers: Box<[Arc<dyn Layer>]>,
251    /// The route's context, built once when the router is built, or `None`
252    /// when it holds no values.
253    context: Option<Arc<ContextMap>>,
254    /// The endpoint serving this route.
255    endpoint: EndpointIndex,
256}
257
258impl RouteWithLayers {
259    /// Returns the endpoint serving this route.
260    pub(crate) fn endpoint(&self) -> EndpointIndex {
261        self.endpoint
262    }
263
264    /// Returns the route's context, or `None` when it holds no values.
265    pub(crate) fn shared_context(&self) -> Option<&Arc<ContextMap>> {
266        self.context.as_ref()
267    }
268}
269
270impl Route for RouteWithLayers {
271    fn id(&self) -> RouteId {
272        self.route.id()
273    }
274
275    fn methods(&self) -> Methods<'_> {
276        self.route.methods()
277    }
278
279    fn path(&self) -> &Path {
280        self.route.path()
281    }
282
283    fn context(&self) -> ContextMap {
284        self.context.as_deref().cloned().unwrap_or_default()
285    }
286
287    fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
288        Next::new(&self.layers, Terminal::Route(&*self.route)).run(cx, body)
289    }
290}
291
292/// A route registered on a [`Router`](crate::Router), as visited by
293/// [`Router::routes`](crate::Router::routes).
294///
295/// It describes the route without a request: the path and methods it is
296/// served at and the values attached to it.
297#[derive(Clone, Copy)]
298pub struct RegisteredRoute<'router> {
299    route: &'router RouteWithLayers,
300    endpoint: &'router Endpoint,
301}
302
303impl<'router> RegisteredRoute<'router> {
304    /// Pairs `route` with the endpoint serving it.
305    pub(crate) fn new(route: &'router RouteWithLayers, endpoint: &'router Endpoint) -> Self {
306        Self { route, endpoint }
307    }
308
309    /// The identity of the route's handler.
310    #[must_use]
311    pub fn id(&self) -> RouteId {
312        self.route.id()
313    }
314
315    /// The URL path the route is served at.
316    ///
317    /// Parameters keep their `{name}` form. Group segments are not part of
318    /// it, because they never reach the URL.
319    #[must_use]
320    pub fn path(&self) -> &'router Path {
321        self.endpoint.path()
322    }
323
324    /// The HTTP methods the route responds to.
325    #[must_use]
326    pub fn methods(&self) -> Methods<'router> {
327        self.route.methods()
328    }
329
330    /// The route's context: the values of the layers and layouts that wrap
331    /// it, followed by its own.
332    ///
333    /// Values registered on the router builder are not part of it. Read them
334    /// with [`Router::context`](crate::Router::context).
335    #[must_use]
336    pub fn context(&self) -> &'router ContextMap {
337        static EMPTY: LazyLock<ContextMap> = LazyLock::new(ContextMap::new);
338        match self.route.shared_context() {
339            Some(context) => context,
340            None => &EMPTY,
341        }
342    }
343}
344
345/// The routes registered on a router, in registration order, indexed by
346/// [`RouteIndex`].
347///
348/// Routes are [`push`](Self::push)ed as the router is built, then only
349/// queried: [`endpoint`](Self::endpoint) resolves a route's [`RouteId`] to the
350/// endpoint serving it, and indexing by [`RouteIndex`] resolves a position
351/// back to the route and its layers.
352#[derive(Default)]
353pub(crate) struct Routes {
354    routes: Vec<RouteWithLayers>,
355    endpoint_lookup: HashMap<RouteId, EndpointIndex>,
356}
357
358impl Routes {
359    /// Registers `route` as served by `endpoint`, wrapped by `layers` and
360    /// with the values of `context`, returning the [`RouteIndex`] that now
361    /// identifies the registration.
362    pub(crate) fn push(
363        &mut self,
364        route: Box<dyn Route>,
365        endpoint: EndpointIndex,
366        layers: Box<[Arc<dyn Layer>]>,
367        context: ContextMap,
368    ) -> RouteIndex {
369        let index = RouteIndex::new(self.routes.len());
370        self.endpoint_lookup.insert(route.id(), endpoint);
371        let context = (!context.is_empty()).then(|| Arc::new(context));
372        self.routes.push(RouteWithLayers {
373            route,
374            layers,
375            context,
376            endpoint,
377        });
378        index
379    }
380
381    /// Returns the endpoint serving the route registered under `id`, or `None` if
382    /// this router holds no route with that identity.
383    pub(crate) fn endpoint(&self, id: RouteId) -> Option<EndpointIndex> {
384        self.endpoint_lookup.get(&id).copied()
385    }
386
387    /// Returns the number of routes registered so far.
388    pub(crate) fn len(&self) -> usize {
389        self.routes.len()
390    }
391
392    /// Iterates over the routes in registration order.
393    pub(crate) fn iter(&self) -> impl Iterator<Item = &RouteWithLayers> {
394        self.routes.iter()
395    }
396}
397
398impl Index<RouteIndex> for Routes {
399    type Output = RouteWithLayers;
400
401    fn index(&self, index: RouteIndex) -> &Self::Output {
402        &self.routes[index.get()]
403    }
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409
410    // -- RouteIndex --
411
412    #[test]
413    fn route_index_wraps_and_unwraps() {
414        let index = RouteIndex::new(7);
415        assert_eq!(index.get(), 7);
416    }
417
418    #[test]
419    fn route_index_zero_is_a_real_index() {
420        // The offset keeps index 0 representable despite the non-zero backing.
421        let index = RouteIndex::new(0);
422        assert_eq!(index.get(), 0);
423    }
424
425    #[test]
426    fn option_route_index_stays_one_word() {
427        assert_eq!(
428            std::mem::size_of::<Option<RouteIndex>>(),
429            std::mem::size_of::<usize>()
430        );
431    }
432}