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,
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, EndpointIndex, HrefTarget, IntoPath, Layer, Methods, Next, OwnedMethods, Path, Terminal,
26    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}
255
256impl RouteWithLayers {
257    /// Returns the route's context, or `None` when it holds no values.
258    pub(crate) fn shared_context(&self) -> Option<&Arc<ContextMap>> {
259        self.context.as_ref()
260    }
261}
262
263impl Route for RouteWithLayers {
264    fn id(&self) -> RouteId {
265        self.route.id()
266    }
267
268    fn methods(&self) -> Methods<'_> {
269        self.route.methods()
270    }
271
272    fn path(&self) -> &Path {
273        self.route.path()
274    }
275
276    fn context(&self) -> ContextMap {
277        self.context.as_deref().cloned().unwrap_or_default()
278    }
279
280    fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
281        Next::new(&self.layers, Terminal::Route(&*self.route)).run(cx, body)
282    }
283}
284
285/// The routes registered on a router, in registration order, indexed by
286/// [`RouteIndex`].
287///
288/// Routes are [`push`](Self::push)ed as the router is built, then only
289/// queried: [`endpoint`](Self::endpoint) resolves a route's [`RouteId`] to the
290/// endpoint serving it, and indexing by [`RouteIndex`] resolves a position
291/// back to the route and its layers.
292#[derive(Default)]
293pub(crate) struct Routes {
294    routes: Vec<RouteWithLayers>,
295    endpoint_lookup: HashMap<RouteId, EndpointIndex>,
296}
297
298impl Routes {
299    /// Registers `route` as served by `endpoint`, wrapped by `layers` and
300    /// with the values of `context`, returning the [`RouteIndex`] that now
301    /// identifies the registration.
302    pub(crate) fn push(
303        &mut self,
304        route: Box<dyn Route>,
305        endpoint: EndpointIndex,
306        layers: Box<[Arc<dyn Layer>]>,
307        context: ContextMap,
308    ) -> RouteIndex {
309        let index = RouteIndex::new(self.routes.len());
310        self.endpoint_lookup.insert(route.id(), endpoint);
311        let context = (!context.is_empty()).then(|| Arc::new(context));
312        self.routes.push(RouteWithLayers {
313            route,
314            layers,
315            context,
316        });
317        index
318    }
319
320    /// Returns the endpoint serving the route registered under `id`, or `None` if
321    /// this router holds no route with that identity.
322    pub(crate) fn endpoint(&self, id: RouteId) -> Option<EndpointIndex> {
323        self.endpoint_lookup.get(&id).copied()
324    }
325}
326
327impl Index<RouteIndex> for Routes {
328    type Output = RouteWithLayers;
329
330    fn index(&self, index: RouteIndex) -> &Self::Output {
331        &self.routes[index.get()]
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    // -- RouteIndex --
340
341    #[test]
342    fn route_index_wraps_and_unwraps() {
343        let index = RouteIndex::new(7);
344        assert_eq!(index.get(), 7);
345    }
346
347    #[test]
348    fn route_index_zero_is_a_real_index() {
349        // The offset keeps index 0 representable despite the non-zero backing.
350        let index = RouteIndex::new(0);
351        assert_eq!(index.get(), 0);
352    }
353
354    #[test]
355    fn option_route_index_stays_one_word() {
356        assert_eq!(
357            std::mem::size_of::<Option<RouteIndex>>(),
358            std::mem::size_of::<usize>()
359        );
360    }
361}