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
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 endpoint: EndpointIndex,
256}
257
258impl RouteWithLayers {
259 pub(crate) fn endpoint(&self) -> EndpointIndex {
261 self.endpoint
262 }
263
264 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#[derive(Clone, Copy)]
298pub struct RegisteredRoute<'router> {
299 route: &'router RouteWithLayers,
300 endpoint: &'router Endpoint,
301}
302
303impl<'router> RegisteredRoute<'router> {
304 pub(crate) fn new(route: &'router RouteWithLayers, endpoint: &'router Endpoint) -> Self {
306 Self { route, endpoint }
307 }
308
309 #[must_use]
311 pub fn id(&self) -> RouteId {
312 self.route.id()
313 }
314
315 #[must_use]
320 pub fn path(&self) -> &'router Path {
321 self.endpoint.path()
322 }
323
324 #[must_use]
326 pub fn methods(&self) -> Methods<'router> {
327 self.route.methods()
328 }
329
330 #[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#[derive(Default)]
353pub(crate) struct Routes {
354 routes: Vec<RouteWithLayers>,
355 endpoint_lookup: HashMap<RouteId, EndpointIndex>,
356}
357
358impl Routes {
359 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 pub(crate) fn endpoint(&self, id: RouteId) -> Option<EndpointIndex> {
384 self.endpoint_lookup.get(&id).copied()
385 }
386
387 pub(crate) fn len(&self) -> usize {
389 self.routes.len()
390 }
391
392 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 #[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 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}