Skip to main content

xitca_router/
error.rs

1use core::{error, fmt, ops::Deref};
2
3use crate::{
4    String, Vec,
5    escape::{UnescapedRef, UnescapedRoute},
6    tree::{Node, denormalize_params},
7};
8
9/// Represents errors that can occur when inserting a new route.
10#[non_exhaustive]
11#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
12pub enum InsertError {
13    /// Attempted to insert a path that conflicts with an existing route.
14    Conflict {
15        /// The existing route that the insertion is conflicting with.
16        with: String,
17    },
18
19    /// Only one parameter per route segment is allowed.
20    ///
21    /// For example, `/foo-{bar}` and `/{bar}-foo` are valid routes, but `/{foo}-{bar}`
22    /// is not.
23    InvalidParamSegment,
24
25    /// Parameters must be registered with a valid name and matching braces.
26    ///
27    /// Note you can use `{{` or `}}` to escape literal brackets.
28    InvalidParam,
29
30    /// Catch-all parameters are only allowed at the end of a path.
31    InvalidCatchAll,
32}
33
34impl fmt::Display for InsertError {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        let fmt = match self {
37            Self::Conflict { with } => {
38                return write!(
39                    f,
40                    "Insertion failed due to conflict with previously registered route: {with}"
41                );
42            }
43            Self::InvalidParamSegment => "Only one parameter is allowed per path segment",
44            Self::InvalidParam => "Parameters must be registered with a valid name",
45            Self::InvalidCatchAll => "Catch-all parameters are only allowed at the end of a route",
46        };
47        fmt::Display::fmt(fmt, f)
48    }
49}
50
51impl error::Error for InsertError {}
52
53impl InsertError {
54    /// Returns an error for a route conflict with the given node.
55    ///
56    /// This method attempts to find the full conflicting route.
57    pub(crate) fn conflict<T>(route: &UnescapedRoute, prefix: UnescapedRef<'_>, current: &Node<T>) -> Self {
58        let mut route = route.clone();
59
60        // The route is conflicting with the current node.
61        if *prefix == *current.prefix {
62            denormalize_params(&mut route, &current.remapping);
63            return InsertError::Conflict {
64                with: route.into_unescaped(),
65            };
66        }
67
68        // Remove the non-matching suffix from the route.
69        route.truncate(route.len() - prefix.len());
70
71        // Add the conflicting prefix.
72        if !route.ends_with(&current.prefix) {
73            route.append(&current.prefix);
74        }
75
76        // Add the prefixes of the first conflicting child.
77        let mut child = current.children.first();
78        while let Some(node) = child {
79            route.append(&node.prefix);
80            child = node.children.first();
81        }
82
83        // Denormalize any route parameters.
84        let mut last = current;
85        while let Some(node) = last.children.first() {
86            last = node;
87        }
88        denormalize_params(&mut route, &last.remapping);
89
90        // Return the conflicting route.
91        InsertError::Conflict {
92            with: route.into_unescaped(),
93        }
94    }
95}
96
97/// A failed merge attempt.
98///
99/// See [`Router::merge`](crate::Router::merge) for details.
100#[derive(Clone, Debug, Eq, PartialEq)]
101pub struct MergeError(pub(crate) Vec<InsertError>);
102
103impl MergeError {
104    /// Returns a list of [`InsertError`] for every insertion that failed
105    /// during the merge.
106    pub fn into_errors(self) -> Vec<InsertError> {
107        self.0
108    }
109}
110
111impl fmt::Display for MergeError {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        for error in self.0.iter() {
114            writeln!(f, "{error}")?;
115        }
116
117        Ok(())
118    }
119}
120
121impl error::Error for MergeError {}
122
123impl Deref for MergeError {
124    type Target = [InsertError];
125
126    fn deref(&self) -> &Self::Target {
127        &self.0
128    }
129}
130
131/// A failed match attempt.
132///
133/// ```
134/// use matchit::{MatchError, Router};
135/// # fn main() -> Result<(), Box<dyn core::error::Error>> {
136/// let mut router = Router::new();
137/// router.insert("/home", "Welcome!")?;
138/// router.insert("/blog", "Our blog.")?;
139///
140/// // no routes match
141/// if let Err(err) = router.at("/blo") {
142///     assert_eq!(err, MatchError::NotFound);
143/// }
144/// # Ok(())
145/// # }
146#[derive(Debug, PartialEq, Eq, Clone, Copy)]
147pub struct MatchError;
148
149impl fmt::Display for MatchError {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        fmt::Display::fmt("Matching route not found", f)
152    }
153}
154
155impl error::Error for MatchError {}