use crate::tree::{denormalize_params, Node}; use std::fmt; /// Represents errors that can occur when inserting a new route. #[non_exhaustive] #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum InsertError { /// Attempted to insert a path that conflicts with an existing route. Conflict { /// The existing route that the insertion is conflicting with. with: String, }, /// Only one parameter per route segment is allowed. TooManyParams, /// Parameters must be registered with a name. UnnamedParam, /// Catch-all parameters are only allowed at the end of a path. InvalidCatchAll, } impl fmt::Display for InsertError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Conflict { with } => { write!( f, "insertion failed due to conflict with previously registered route: {}", with ) } Self::TooManyParams => write!(f, "only one parameter is allowed per path segment"), Self::UnnamedParam => write!(f, "parameters must be registered with a name"), Self::InvalidCatchAll => write!( f, "catch-all parameters are only allowed at the end of a route" ), } } } impl std::error::Error for InsertError {} impl InsertError { pub(crate) fn conflict(route: &[u8], prefix: &[u8], current: &Node) -> Self { let mut route = route[..route.len() - prefix.len()].to_owned(); if !route.ends_with(¤t.prefix) { route.extend_from_slice(¤t.prefix); } let mut last = current; while let Some(node) = last.children.first() { last = node; } let mut current = current.children.first(); while let Some(node) = current { route.extend_from_slice(&node.prefix); current = node.children.first(); } denormalize_params(&mut route, &last.param_remapping); InsertError::Conflict { with: String::from_utf8(route).unwrap(), } } } /// A failed match attempt. /// /// ``` /// use matchit::{MatchError, Router}; /// # fn main() -> Result<(), Box> { /// let mut router = Router::new(); /// router.insert("/home", "Welcome!")?; /// router.insert("/blog/", "Our blog.")?; /// /// // a route exists without the trailing slash /// if let Err(err) = router.at("/home/") { /// assert_eq!(err, MatchError::ExtraTrailingSlash); /// } /// /// // a route exists with a trailing slash /// if let Err(err) = router.at("/blog") { /// assert_eq!(err, MatchError::MissingTrailingSlash); /// } /// /// // no routes match /// if let Err(err) = router.at("/foobar") { /// assert_eq!(err, MatchError::NotFound); /// } /// # Ok(()) /// # } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum MatchError { /// The path was missing a trailing slash. MissingTrailingSlash, /// The path had an extra trailing slash. ExtraTrailingSlash, /// No matching route was found. NotFound, } impl MatchError { pub(crate) fn unsure(full_path: &[u8]) -> Self { if full_path[full_path.len() - 1] == b'/' { MatchError::ExtraTrailingSlash } else { MatchError::MissingTrailingSlash } } } impl fmt::Display for MatchError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let msg = match self { MatchError::MissingTrailingSlash => "match error: expected trailing slash", MatchError::ExtraTrailingSlash => "match error: found extra trailing slash", MatchError::NotFound => "match error: route not found", }; write!(f, "{}", msg) } } impl std::error::Error for MatchError {}