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