xitca_router/
error.rs

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