1#[cfg(feature = "fs")]
2mod directory;
3
4use std::{
5 borrow::Cow,
6 collections::HashMap,
7 num::NonZeroUsize,
8 ops::Index,
9 pin::Pin,
10 sync::{
11 Arc,
12 atomic::{AtomicUsize, Ordering},
13 },
14};
15
16#[cfg(feature = "fs")]
17pub use directory::*;
18use topcoat_core::{context::Cx, error::Result};
19
20use crate::{
21 Body, EndpointIndex, HrefTarget, IntoPath, Layer, Methods, Next, OwnedMethods, Path, Terminal,
22 response::Response, route, route_endpoint,
23};
24
25pub type RouteFuture<'cx> = Pin<Box<dyn Future<Output = Result<Response>> + Send + 'cx>>;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub struct RouteId(usize);
35
36impl RouteId {
37 #[must_use]
41 #[allow(clippy::new_without_default)]
42 pub fn new() -> Self {
43 static NEXT: AtomicUsize = AtomicUsize::new(0);
44 Self(NEXT.fetch_add(1, Ordering::Relaxed))
45 }
46}
47
48pub trait Route: Send + Sync + 'static {
54 fn id(&self) -> RouteId;
56
57 fn methods(&self) -> Methods<'_>;
59
60 fn path(&self) -> &Path;
62
63 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx>;
65
66 fn is_current(&self, cx: &Cx) -> bool {
76 route(cx).id() == self.id()
77 }
78}
79
80impl<R: Route + ?Sized> Route for &'static R {
81 fn id(&self) -> RouteId {
82 (**self).id()
83 }
84
85 fn methods(&self) -> Methods<'_> {
86 (**self).methods()
87 }
88
89 fn path(&self) -> &Path {
90 (**self).path()
91 }
92
93 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
94 (**self).handle(cx, body)
95 }
96}
97
98#[cfg(feature = "discover")]
99inventory::collect!(&'static dyn Route);
100
101pub type RouteHandlerFn = for<'cx> fn(cx: &'cx Cx, body: Body) -> RouteFuture<'cx>;
103
104#[derive(Debug, Clone)]
109pub struct RouteFn {
110 id: RouteId,
112 methods: OwnedMethods,
114 path: Cow<'static, Path>,
116 handle: RouteHandlerFn,
118}
119
120impl RouteFn {
121 #[track_caller]
144 pub fn new(
145 methods: impl Into<OwnedMethods>,
146 path: impl IntoPath,
147 handle: RouteHandlerFn,
148 ) -> Self {
149 Self {
150 id: RouteId::new(),
151 methods: methods.into(),
152 path: path.into_path(),
153 handle,
154 }
155 }
156}
157
158impl Route for RouteFn {
159 fn id(&self) -> RouteId {
160 self.id
161 }
162
163 fn methods(&self) -> Methods<'_> {
164 self.methods.as_methods()
165 }
166
167 fn path(&self) -> &Path {
168 &self.path
169 }
170
171 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
172 (self.handle)(cx, body)
173 }
174}
175
176impl HrefTarget for RouteFn {
177 #[track_caller]
178 fn path<'cx>(&self, cx: &'cx Cx) -> &'cx Path {
179 match route_endpoint(cx, self.id) {
180 Some(endpoint) => endpoint.path(),
181 None => panic!(
182 "route `{}` is not registered on the router serving this request",
183 self.path
184 ),
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub(crate) struct RouteIndex(NonZeroUsize);
195
196impl RouteIndex {
197 pub(crate) fn new(index: usize) -> Self {
199 Self(NonZeroUsize::new(index.wrapping_add(1)).expect("route index overflow"))
200 }
201
202 pub(crate) fn get(self) -> usize {
204 self.0.get() - 1
205 }
206}
207
208pub(crate) struct RouteWithLayers {
210 route: Box<dyn Route>,
212 layers: Box<[Arc<dyn Layer>]>,
216}
217
218impl Route for RouteWithLayers {
219 fn id(&self) -> RouteId {
220 self.route.id()
221 }
222
223 fn methods(&self) -> Methods<'_> {
224 self.route.methods()
225 }
226
227 fn path(&self) -> &Path {
228 self.route.path()
229 }
230
231 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx> {
232 Next::new(&self.layers, Terminal::Route(&*self.route)).run(cx, body)
233 }
234}
235
236#[derive(Default)]
244pub(crate) struct Routes {
245 routes: Vec<RouteWithLayers>,
246 endpoint_lookup: HashMap<RouteId, EndpointIndex>,
247}
248
249impl Routes {
250 pub(crate) fn push(
253 &mut self,
254 route: Box<dyn Route>,
255 endpoint: EndpointIndex,
256 layers: Box<[Arc<dyn Layer>]>,
257 ) -> RouteIndex {
258 let index = RouteIndex::new(self.routes.len());
259 self.endpoint_lookup.insert(route.id(), endpoint);
260 self.routes.push(RouteWithLayers { route, layers });
261 index
262 }
263
264 pub(crate) fn endpoint(&self, id: RouteId) -> Option<EndpointIndex> {
267 self.endpoint_lookup.get(&id).copied()
268 }
269}
270
271impl Index<RouteIndex> for Routes {
272 type Output = RouteWithLayers;
273
274 fn index(&self, index: RouteIndex) -> &Self::Output {
275 &self.routes[index.get()]
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
286 fn route_index_wraps_and_unwraps() {
287 let index = RouteIndex::new(7);
288 assert_eq!(index.get(), 7);
289 }
290
291 #[test]
292 fn route_index_zero_is_a_real_index() {
293 let index = RouteIndex::new(0);
295 assert_eq!(index.get(), 0);
296 }
297
298 #[test]
299 fn option_route_index_stays_one_word() {
300 assert_eq!(
301 std::mem::size_of::<Option<RouteIndex>>(),
302 std::mem::size_of::<usize>()
303 );
304 }
305}