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
29pub type RouteFuture<'cx> = Pin<Box<dyn Future<Output = Result<Response>> + Send + 'cx>>;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct RouteId(usize);
39
40impl RouteId {
41 #[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
52pub trait Route: Send + Sync + 'static {
58 fn id(&self) -> RouteId;
60
61 fn methods(&self) -> Methods<'_>;
63
64 fn path(&self) -> &Path;
66
67 fn context(&self) -> ContextMap {
71 ContextMap::new()
72 }
73
74 fn handle<'cx>(&'cx self, cx: &'cx Cx, body: Body) -> RouteFuture<'cx>;
76
77 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
116pub type RouteHandlerFn = for<'cx> fn(cx: &'cx Cx, body: Body) -> RouteFuture<'cx>;
118
119#[derive(Debug, Clone)]
124pub struct RouteFn {
125 id: RouteId,
127 methods: OwnedMethods,
129 path: Cow<'static, Path>,
131 handle: RouteHandlerFn,
133 context: ContextMap,
135}
136
137impl RouteFn {
138 #[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub(crate) struct RouteIndex(NonZeroUsize);
230
231impl RouteIndex {
232 pub(crate) fn new(index: usize) -> Self {
234 Self(NonZeroUsize::new(index.wrapping_add(1)).expect("route index overflow"))
235 }
236
237 pub(crate) fn get(self) -> usize {
239 self.0.get() - 1
240 }
241}
242
243pub(crate) struct RouteWithLayers {
245 route: Box<dyn Route>,
247 layers: Box<[Arc<dyn Layer>]>,
251 context: Option<Arc<ContextMap>>,
254}
255
256impl RouteWithLayers {
257 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#[derive(Default)]
293pub(crate) struct Routes {
294 routes: Vec<RouteWithLayers>,
295 endpoint_lookup: HashMap<RouteId, EndpointIndex>,
296}
297
298impl Routes {
299 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 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 #[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 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}