use super::{imp, Params};
use thiserror::Error;
pub struct RouteMatch<T> {
pub value: T,
pub params: Params,
}
pub struct PathRouter<T> {
imp: imp::BaseRouter<T>,
}
impl<T> PathRouter<T> {
pub fn new() -> Self {
PathRouter {
imp: imp::BaseRouter::new(),
}
}
pub fn insert(&mut self, route: impl Into<String>, value: T) -> Result<(), InsertError> {
let route = route.into();
assert_valid_route(&route).map_err(|err| InsertError(err.into()))?;
self.imp.insert(route, value)
}
pub fn find(&self, path: impl AsRef<str>) -> Result<RouteMatch<&T>, MatchError> {
self.imp.find(path)
}
pub fn find_mut(&mut self, path: impl AsRef<str>) -> Result<RouteMatch<&mut T>, MatchError> {
self.imp.find_mut(path)
}
}
impl<T> Default for PathRouter<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Error)]
#[error(transparent)]
pub struct InsertError(pub(crate) Box<dyn std::error::Error + Send + Sync>);
#[derive(Debug, Error)]
pub enum MatchError {
#[error("route not found")]
NotFound,
#[error(transparent)]
Other(Box<dyn std::error::Error + Send + Sync>),
}
pub(crate) fn assert_valid_route(path: &str) -> Result<(), String> {
if path.is_empty() {
return Err(String::from("route path cannot be empty"));
}
if path.starts_with(' ') || path.ends_with(' ') {
return Err(format!(
"route path cannot starts or end with a whitespace but was: {}",
path
));
}
if !path.starts_with('/') {
return Err(format!("route path must start with `/`, but was: {}", path));
}
if path.len() > 1 && path.ends_with('/') {
return Err(format!("route path cannot end with `/` but was: {}", path));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_route() {
let result = assert_valid_route("/test");
assert!(result.is_ok());
}
#[test]
fn test_invalid_empty_route() {
let result = assert_valid_route("");
assert_eq!(
result.err(),
Some(String::from("route path cannot be empty"))
);
}
#[test]
fn test_invalid_whitespace_route() {
let result = assert_valid_route(" /test");
assert_eq!(
result.err(),
Some(String::from(
"route path cannot starts or end with a whitespace but was: /test"
))
);
}
#[test]
fn test_invalid_starting_char_route() {
let result = assert_valid_route("test");
assert_eq!(
result.err(),
Some(String::from(
"route path must start with `/`, but was: test"
))
);
}
#[test]
fn test_invalid_ending_char_route() {
let result = assert_valid_route("/test/");
assert_eq!(
result.err(),
Some(String::from(
"route path cannot end with `/` but was: /test/"
))
);
}
#[test]
fn test_insert() {
let mut router = PathRouter::new();
let route = "/test/:param1/:param2";
let value = "test-value";
let result = router.insert(route, value);
assert!(result.is_ok());
}
#[test]
fn test_find_match() {
let mut router = PathRouter::new();
let route = "/test/:param1/:param2";
let value = "test-value";
router.insert(route, value).unwrap();
let result = router.find("/test/abc/def");
assert!(result.is_ok());
let match_result = result.unwrap();
assert_eq!(
match_result.params.get("param1"),
Some(String::from("abc").as_str())
);
assert_eq!(
match_result.params.get("param2"),
Some(String::from("def").as_str())
);
assert_eq!(*match_result.value, "test-value");
}
#[test]
fn test_find_match_not_found() {
let router = PathRouter::<&str>::new();
let result = router.find("/not-found");
assert!(matches!(result.err().unwrap(), MatchError::NotFound));
}
}