xitca_router/
router.rs

1use crate::error::MergeError;
2use crate::tree::Node;
3use crate::{params::Params, InsertError, MatchError};
4
5/// A zero-copy URL router.
6///
7/// See [the crate documentation](crate) for details.
8#[derive(Clone, Debug)]
9pub struct Router<T> {
10    root: Node<T>,
11}
12
13impl<T> Default for Router<T> {
14    fn default() -> Self {
15        Self { root: Node::default() }
16    }
17}
18
19impl<T> Router<T> {
20    /// Construct a new router.
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Insert a route into the router.
26    ///
27    /// # Examples
28    ///
29    /// ```rust
30    /// # use xitca_router::Router;
31    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
32    /// let mut router = Router::new();
33    /// router.insert("/home", "Welcome!")?;
34    /// router.insert("/users/{id}", "A User")?;
35    /// # Ok(())
36    /// # }
37    /// ```
38    pub fn insert(&mut self, route: impl Into<String>, value: T) -> Result<(), InsertError> {
39        self.root.insert(route.into(), value)
40    }
41
42    /// Tries to find a value in the router matching the given path.
43    ///
44    /// # Examples
45    ///
46    /// ```rust
47    /// # use xitca_router::Router;
48    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
49    /// let mut router = Router::new();
50    /// router.insert("/home", "Welcome!")?;
51    ///
52    /// let matched = router.at("/home").unwrap();
53    /// assert_eq!(*matched.value, "Welcome!");
54    /// # Ok(())
55    /// # }
56    /// ```
57    #[inline]
58    pub fn at(&self, path: &str) -> Result<Match<&T>, MatchError> {
59        self.root.at(path).map(|(value, params)| Match { value, params })
60    }
61
62    /// Remove a given route from the router.
63    ///
64    /// Returns the value stored under the route if it was found.
65    /// If the route was not found or invalid, `None` is returned.
66    ///
67    /// # Examples
68    ///
69    /// ```rust
70    /// # use xitca_router::Router;
71    /// let mut router = Router::new();
72    ///
73    /// router.insert("/home", "Welcome!");
74    /// assert_eq!(router.remove("/home"), Some("Welcome!"));
75    /// assert_eq!(router.remove("/home"), None);
76    ///
77    /// router.insert("/home/{id}/", "Hello!");
78    /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!"));
79    /// assert_eq!(router.remove("/home/{id}/"), None);
80    ///
81    /// router.insert("/home/{id}/", "Hello!");
82    /// // The route does not match.
83    /// assert_eq!(router.remove("/home/{user}"), None);
84    /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!"));
85    ///
86    /// router.insert("/home/{id}/", "Hello!");
87    /// // Invalid route.
88    /// assert_eq!(router.remove("/home/{id}"), None);
89    /// assert_eq!(router.remove("/home/{id}/"), Some("Hello!"));
90    /// ```
91    pub fn remove(&mut self, path: impl Into<String>) -> Option<T> {
92        self.root.remove(path.into())
93    }
94
95    #[doc(hidden)]
96    /// Test helper that ensures route priorities are consistent.
97    pub fn check_priorities(&self) -> Result<u32, (u32, u32)> {
98        self.root.check_priorities()
99    }
100
101    /// Merge a given router into current one.
102    ///
103    /// Returns a list of [`InsertError`] for every failed insertion.
104    /// Note that this can result in a partially successful merge if
105    /// a subset of routes conflict.
106    ///
107    /// # Examples
108    ///
109    /// ```rust
110    /// # use xitca_router::Router;
111    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
112    /// let mut root = Router::new();
113    /// root.insert("/home", "Welcome!")?;
114    ///
115    /// let mut child = Router::new();
116    /// child.insert("/users/{id}", "A User")?;
117    ///
118    /// root.merge(child)?;
119    /// assert!(root.at("/users/1").is_ok());
120    /// # Ok(())
121    /// # }
122    /// ```
123    pub fn merge(&mut self, other: Self) -> Result<(), MergeError> {
124        let mut errors = Vec::new();
125        other.root.for_each(|path, value| {
126            if let Err(err) = self.insert(path, value) {
127                errors.push(err);
128            }
129        });
130
131        if errors.is_empty() {
132            Ok(())
133        } else {
134            Err(MergeError(errors))
135        }
136    }
137}
138
139/// A successful match consisting of the registered value
140/// and URL parameters, returned by [`Router::at`](Router::at).
141#[derive(Debug)]
142pub struct Match<V> {
143    /// The value stored under the matched node.
144    pub value: V,
145    /// The route parameters. See [parameters](crate#parameters) for more details.
146    pub params: Params,
147}