matchthem/
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!(
46                f,
47                "Catch-all parameters are only allowed at the end of a route"
48            ),
49        }
50    }
51}
52
53impl std::error::Error for InsertError {}
54
55impl InsertError {
56    /// Returns an error for a route conflict with the given node.
57    ///
58    /// This method attempts to find the full conflicting route.
59    pub(crate) fn conflict<T>(
60        route: &UnescapedRoute,
61        prefix: UnescapedRef<'_>,
62        current: &Node<T>,
63    ) -> Self {
64        let mut route = route.clone();
65
66        // The route is conflicting with the current node.
67        if prefix.unescaped() == current.prefix.unescaped() {
68            denormalize_params(&mut route, &current.remapping);
69            return InsertError::Conflict {
70                with: String::from_utf8(route.into_unescaped()).unwrap(),
71            };
72        }
73
74        // Remove the non-matching suffix from the route.
75        route.truncate(route.len() - prefix.len());
76
77        // Add the conflicting prefix.
78        if !route.ends_with(&current.prefix) {
79            route.append(&current.prefix);
80        }
81
82        // Add the prefixes of any conflicting children.
83        let mut child = current.children.first();
84        while let Some(node) = child {
85            route.append(&node.prefix);
86            child = node.children.first();
87        }
88
89        // Denormalize any route parameters.
90        let mut last = current;
91        while let Some(node) = last.children.first() {
92            last = node;
93        }
94        denormalize_params(&mut route, &last.remapping);
95
96        // Return the conflicting route.
97        InsertError::Conflict {
98            with: String::from_utf8(route.into_unescaped()).unwrap(),
99        }
100    }
101}
102
103/// A failed merge attempt.
104///
105/// See [`Router::merge`](crate::Router::merge) for details.
106#[derive(Clone, Debug, Eq, PartialEq)]
107pub struct MergeError(pub(crate) Vec<InsertError>);
108
109impl MergeError {
110    /// Returns a list of [`InsertError`] for every insertion that failed
111    /// during the merge.
112    pub fn into_errors(self) -> Vec<InsertError> {
113        self.0
114    }
115}
116
117impl fmt::Display for MergeError {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        for error in self.0.iter() {
120            writeln!(f, "{}", error)?;
121        }
122
123        Ok(())
124    }
125}
126
127impl std::error::Error for MergeError {}
128
129impl Deref for MergeError {
130    type Target = Vec<InsertError>;
131
132    fn deref(&self) -> &Self::Target {
133        &self.0
134    }
135}
136
137/// A failed match attempt.
138///
139/// ```
140/// use matchit::{MatchError, Router};
141/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
142/// let mut router = Router::new();
143/// router.insert("/home", "Welcome!")?;
144/// router.insert("/blog", "Our blog.")?;
145///
146/// // no routes match
147/// if let Err(err) = router.at("/blo") {
148///     assert_eq!(err, MatchError::NotFound);
149/// }
150/// # Ok(())
151/// # }
152#[derive(Debug, PartialEq, Eq, Clone, Copy)]
153pub enum MatchError {
154    /// No matching route was found.
155    NotFound,
156}
157
158impl fmt::Display for MatchError {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        write!(f, "Matching route not found")
161    }
162}
163
164impl std::error::Error for MatchError {}