matchit/
error.rs

1use crate::escape::{UnescapedRef, UnescapedRoute};
2use crate::tree::{denormalize_params, Node};
3
4use std::fmt;
5
6/// Represents errors that can occur when inserting a new route.
7#[non_exhaustive]
8#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum InsertError {
10    /// Attempted to insert a path that conflicts with an existing route.
11    Conflict {
12        /// The existing route that the insertion is conflicting with.
13        with: String,
14    },
15    /// Only one parameter per route segment is allowed.
16    ///
17    /// Static segments are also allowed before a parameter, but not after it. For example,
18    /// `/foo-{bar}` is a valid route, but `/{bar}-foo` is not.
19    InvalidParamSegment,
20    /// Parameters must be registered with a valid name and matching braces.
21    ///
22    /// Note you can use `{{` or `}}` to escape literal brackets.
23    InvalidParam,
24    /// Catch-all parameters are only allowed at the end of a path.
25    InvalidCatchAll,
26}
27
28impl fmt::Display for InsertError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            Self::Conflict { with } => {
32                write!(
33                    f,
34                    "Insertion failed due to conflict with previously registered route: {}",
35                    with
36                )
37            }
38            Self::InvalidParamSegment => {
39                write!(f, "Only one parameter is allowed per path segment")
40            }
41            Self::InvalidParam => write!(f, "Parameters must be registered with a valid name"),
42            Self::InvalidCatchAll => write!(
43                f,
44                "Catch-all parameters are only allowed at the end of a route"
45            ),
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>(
57        route: &UnescapedRoute,
58        prefix: UnescapedRef<'_>,
59        current: &Node<T>,
60    ) -> Self {
61        let mut route = route.clone();
62
63        // The route is conflicting with the current node.
64        if prefix.unescaped() == current.prefix.unescaped() {
65            denormalize_params(&mut route, &current.remapping);
66            return InsertError::Conflict {
67                with: String::from_utf8(route.into_unescaped()).unwrap(),
68            };
69        }
70
71        // Remove the non-matching suffix from the route.
72        route.truncate(route.len() - prefix.len());
73
74        // Add the conflicting prefix.
75        if !route.ends_with(&current.prefix) {
76            route.append(&current.prefix);
77        }
78
79        // Add the prefixes of any conflicting children.
80        let mut child = current.children.first();
81        while let Some(node) = child {
82            route.append(&node.prefix);
83            child = node.children.first();
84        }
85
86        // Denormalize any route parameters.
87        let mut last = current;
88        while let Some(node) = last.children.first() {
89            last = node;
90        }
91        denormalize_params(&mut route, &last.remapping);
92
93        // Return the conflicting route.
94        InsertError::Conflict {
95            with: String::from_utf8(route.into_unescaped()).unwrap(),
96        }
97    }
98}
99
100/// A failed match attempt.
101///
102/// ```
103/// use matchit::{MatchError, Router};
104/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
105/// let mut router = Router::new();
106/// router.insert("/home", "Welcome!")?;
107/// router.insert("/blog", "Our blog.")?;
108///
109/// // no routes match
110/// if let Err(err) = router.at("/blo") {
111///     assert_eq!(err, MatchError::NotFound);
112/// }
113/// # Ok(())
114/// # }
115#[derive(Debug, PartialEq, Eq, Clone, Copy)]
116pub enum MatchError {
117    /// No matching route was found.
118    NotFound,
119}
120
121impl fmt::Display for MatchError {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        write!(f, "Matching route not found")
124    }
125}
126
127impl std::error::Error for MatchError {}