intrepid_core/routing/
routes.rs

1use std::{collections::HashMap, sync::Arc};
2use uuid::Uuid;
3
4use super::path_error::PathError;
5
6/// A route ID is a unique identifier for a route. This is a wrapper around a UUID.
7#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
8#[repr(transparent)]
9pub struct RouteId(Uuid);
10
11impl From<Uuid> for RouteId {
12    fn from(uuid: Uuid) -> Self {
13        Self(uuid)
14    }
15}
16
17/// A collection of routes that can be mounted on a router. This isn't where the endpoints
18/// are stored, but is instead an index to track those endpoints.
19#[derive(Clone, Default, Debug)]
20pub struct Routes {
21    router: path_tree::PathTree<RouteId>,
22    paths_by_id: HashMap<RouteId, Arc<str>>,
23    ids_by_path: HashMap<Arc<str>, RouteId>,
24}
25
26impl Routes {
27    /// Get the route ID for a given path.
28    pub fn at<'route, 'path>(
29        &'route self,
30        path: &'path str,
31    ) -> Option<(&'route RouteId, path_tree::Path<'route, 'path>)> {
32        self.router.find(path)
33    }
34
35    /// Insert a route into the router.
36    pub fn insert(&mut self, route: impl Into<String>, value: RouteId) {
37        let route = route.into();
38
39        let _index = self.router.insert(route.as_str(), value);
40        let shared_path: Arc<str> = route.into();
41
42        self.paths_by_id.insert(value, shared_path.clone());
43        self.ids_by_path.insert(shared_path, value);
44    }
45
46    /// Get the route ID for a given path.
47    pub fn get_id(&self, path: &str) -> Option<RouteId> {
48        self.ids_by_path.get(path).copied()
49    }
50
51    /// Get the path for a given route ID.
52    pub fn get_path(&self, id: RouteId) -> Option<&str> {
53        self.paths_by_id.get(&id).map(|path| path.as_ref())
54    }
55
56    /// Capture the values from a path.
57    pub fn capture<T>(&self, path: impl AsRef<str>) -> Result<T, PathError>
58    where
59        T: serde::de::DeserializeOwned,
60    {
61        let Some((_route_id, captures)) = self.router.find(path.as_ref()) else {
62            return Err(PathError::FailedToCapture(path.as_ref().to_string()));
63        };
64
65        let mut raws = Vec::new();
66
67        for value in &captures.raws {
68            raws.push(value);
69        }
70
71        let slice = serde_json::to_vec(&raws)?;
72
73        Ok(serde_json::from_slice(&slice)?)
74    }
75
76    /// Get the paths that have been mounted so far.
77    pub fn paths(&self) -> Vec<String> {
78        let mut routes: Vec<String> = self
79            .ids_by_path
80            .keys()
81            .map(|path| path.to_string())
82            .collect();
83
84        routes.sort();
85        routes
86    }
87}
88
89#[test]
90fn grabbing_path_captures() -> Result<(), tower::BoxError> {
91    let mut routes = Routes::default();
92    let route_id = RouteId::from(uuid::Uuid::new_v4());
93
94    routes.insert("/view/:id/children/:child_id/add/:new_child_name", route_id);
95
96    let uuid_one = uuid::Uuid::new_v4();
97    let uuid_two = uuid::Uuid::new_v4();
98    let uuid_three = uuid::Uuid::new_v4();
99    let path = format!("/view/{uuid_one}/children/{uuid_two}/add/{uuid_three}");
100
101    let uuids: (uuid::Uuid, uuid::Uuid, uuid::Uuid) = routes.capture(path)?;
102
103    assert_eq!(uuids, (uuid_one, uuid_two, uuid_three));
104
105    Ok(())
106}