intrepid_core/routing/
routes.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use std::{collections::HashMap, sync::Arc};
use uuid::Uuid;

use super::path_error::PathError;

/// A route ID is a unique identifier for a route. This is a wrapper around a UUID.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct RouteId(Uuid);

impl From<Uuid> for RouteId {
    fn from(uuid: Uuid) -> Self {
        Self(uuid)
    }
}

/// A collection of routes that can be mounted on a router. This isn't where the endpoints
/// are stored, but is instead an index to track those endpoints.
#[derive(Clone, Default, Debug)]
pub struct Routes {
    router: path_tree::PathTree<RouteId>,
    paths_by_id: HashMap<RouteId, Arc<str>>,
    ids_by_path: HashMap<Arc<str>, RouteId>,
}

impl Routes {
    /// Get the route ID for a given path.
    pub fn at<'route, 'path>(
        &'route self,
        path: &'path str,
    ) -> Option<(&'route RouteId, path_tree::Path<'route, 'path>)> {
        self.router.find(path)
    }

    /// Insert a route into the router.
    pub fn insert(&mut self, route: impl Into<String>, value: RouteId) {
        let route = route.into();

        let _index = self.router.insert(route.as_str(), value);
        let shared_path: Arc<str> = route.into();

        self.paths_by_id.insert(value, shared_path.clone());
        self.ids_by_path.insert(shared_path, value);
    }

    /// Get the route ID for a given path.
    pub fn get_id(&self, path: &str) -> Option<RouteId> {
        self.ids_by_path.get(path).copied()
    }

    /// Get the path for a given route ID.
    pub fn get_path(&self, id: RouteId) -> Option<&str> {
        self.paths_by_id.get(&id).map(|path| path.as_ref())
    }

    /// Capture the values from a path.
    pub fn capture<T>(&self, path: impl AsRef<str>) -> Result<T, PathError>
    where
        T: serde::de::DeserializeOwned,
    {
        let Some((_route_id, captures)) = self.router.find(path.as_ref()) else {
            return Err(PathError::FailedToCapture(path.as_ref().to_string()));
        };

        let mut raws = Vec::new();

        for value in &captures.raws {
            raws.push(value);
        }

        let slice = serde_json::to_vec(&raws)?;

        Ok(serde_json::from_slice(&slice)?)
    }

    /// Get the paths that have been mounted so far.
    pub fn paths(&self) -> Vec<String> {
        let mut routes: Vec<String> = self
            .ids_by_path
            .keys()
            .map(|path| path.to_string())
            .collect();

        routes.sort();
        routes
    }
}

#[test]
fn grabbing_path_captures() -> Result<(), tower::BoxError> {
    let mut routes = Routes::default();
    let route_id = RouteId::from(uuid::Uuid::new_v4());

    routes.insert("/view/:id/children/:child_id/add/:new_child_name", route_id);

    let uuid_one = uuid::Uuid::new_v4();
    let uuid_two = uuid::Uuid::new_v4();
    let uuid_three = uuid::Uuid::new_v4();
    let path = format!("/view/{uuid_one}/children/{uuid_two}/add/{uuid_three}");

    let uuids: (uuid::Uuid, uuid::Uuid, uuid::Uuid) = routes.capture(path)?;

    assert_eq!(uuids, (uuid_one, uuid_two, uuid_three));

    Ok(())
}