use std::fmt::{self, Formatter};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use super::filters;
use super::{Filter, FnFilter, PathFilter, PathState};
use crate::handler::{Handler, WhenHoop};
use crate::http::uri::Scheme;
use crate::{Depot, Request};
#[non_exhaustive]
pub struct Router {
#[doc(hidden)]
pub id: usize,
pub routers: Vec<Router>,
pub filters: Vec<Box<dyn Filter>>,
pub hoops: Vec<Arc<dyn Handler>>,
pub goal: Option<Arc<dyn Handler>>,
}
#[doc(hidden)]
pub struct DetectMatched {
pub hoops: Vec<Arc<dyn Handler>>,
pub goal: Arc<dyn Handler>,
}
impl Default for Router {
#[inline]
fn default() -> Self {
Self::new()
}
}
static NEXT_ROUTER_ID: AtomicUsize = AtomicUsize::new(1);
impl Router {
#[inline]
pub fn new() -> Self {
Self {
id: NEXT_ROUTER_ID.fetch_add(1, Ordering::Relaxed),
routers: Vec::new(),
filters: Vec::new(),
hoops: Vec::new(),
goal: None,
}
}
#[inline]
pub fn routers(&self) -> &Vec<Router> {
&self.routers
}
#[inline]
pub fn routers_mut(&mut self) -> &mut Vec<Router> {
&mut self.routers
}
#[inline]
pub fn hoops(&self) -> &Vec<Arc<dyn Handler>> {
&self.hoops
}
#[inline]
pub fn hoops_mut(&mut self) -> &mut Vec<Arc<dyn Handler>> {
&mut self.hoops
}
#[inline]
pub fn filters(&self) -> &Vec<Box<dyn Filter>> {
&self.filters
}
#[inline]
pub fn filters_mut(&mut self) -> &mut Vec<Box<dyn Filter>> {
&mut self.filters
}
pub fn detect(&self, req: &mut Request, path_state: &mut PathState) -> Option<DetectMatched> {
for filter in &self.filters {
if !filter.filter(req, path_state) {
return None;
}
}
if !self.routers.is_empty() {
let original_cursor = path_state.cursor;
for child in &self.routers {
if let Some(dm) = child.detect(req, path_state) {
return Some(DetectMatched {
hoops: [&self.hoops[..], &dm.hoops[..]].concat(),
goal: dm.goal.clone(),
});
} else {
path_state.cursor = original_cursor;
}
}
}
if let Some(goal) = self.goal.clone() {
if path_state.is_ended() {
return Some(DetectMatched {
hoops: self.hoops.clone(),
goal,
});
}
}
None
}
#[inline]
pub fn unshift(mut self, router: Router) -> Self {
self.routers.insert(0, router);
self
}
#[inline]
pub fn insert(mut self, index: usize, router: Router) -> Self {
self.routers.insert(index, router);
self
}
#[inline]
pub fn push(mut self, router: Router) -> Self {
self.routers.push(router);
self
}
#[inline]
pub fn append(mut self, others: &mut Vec<Router>) -> Self {
self.routers.append(others);
self
}
#[inline]
pub fn with_hoop<H: Handler>(hoop: H) -> Self {
Router::new().hoop(hoop)
}
#[inline]
pub fn with_hoop_when<H, F>(hoop: H, filter: F) -> Self
where
H: Handler,
F: Fn(&Request, &Depot) -> bool + Send + Sync + 'static,
{
Router::new().hoop_when(hoop, filter)
}
#[inline]
pub fn hoop<H: Handler>(mut self, hoop: H) -> Self {
self.hoops.push(Arc::new(hoop));
self
}
#[inline]
pub fn hoop_when<H, F>(mut self, hoop: H, filter: F) -> Self
where
H: Handler,
F: Fn(&Request, &Depot) -> bool + Send + Sync + 'static,
{
self.hoops.push(Arc::new(WhenHoop { inner: hoop, filter }));
self
}
#[inline]
pub fn with_path(path: impl Into<String>) -> Self {
Router::with_filter(PathFilter::new(path))
}
#[inline]
pub fn path(self, path: impl Into<String>) -> Self {
self.filter(PathFilter::new(path))
}
#[inline]
pub fn with_filter(filter: impl Filter + Sized) -> Self {
Router::new().filter(filter)
}
#[inline]
pub fn filter(mut self, filter: impl Filter + Sized) -> Self {
self.filters.push(Box::new(filter));
self
}
#[inline]
pub fn with_filter_fn<T>(func: T) -> Self
where
T: Fn(&mut Request, &mut PathState) -> bool + Send + Sync + 'static,
{
Router::with_filter(FnFilter(func))
}
#[inline]
pub fn filter_fn<T>(self, func: T) -> Self
where
T: Fn(&mut Request, &mut PathState) -> bool + Send + Sync + 'static,
{
self.filter(FnFilter(func))
}
#[inline]
pub fn goal<H: Handler>(mut self, goal: H) -> Self {
self.goal = Some(Arc::new(goal));
self
}
#[inline]
pub fn then<F>(self, func: F) -> Self
where
F: FnOnce(Self) -> Self,
{
func(self)
}
#[inline]
pub fn scheme(self, scheme: Scheme) -> Self {
self.filter(filters::scheme(scheme))
}
#[inline]
pub fn host(self, host: impl Into<String>) -> Self {
self.filter(filters::host(host))
}
#[inline]
pub fn port(self, port: u16) -> Self {
self.filter(filters::port(port))
}
#[inline]
pub fn get<H: Handler>(self, goal: H) -> Self {
self.push(Router::with_filter(filters::get()).goal(goal))
}
#[inline]
pub fn post<H: Handler>(self, goal: H) -> Self {
self.push(Router::with_filter(filters::post()).goal(goal))
}
#[inline]
pub fn put<H: Handler>(self, goal: H) -> Self {
self.push(Router::with_filter(filters::put()).goal(goal))
}
#[inline]
pub fn delete<H: Handler>(self, goal: H) -> Self {
self.push(Router::with_filter(filters::delete()).goal(goal))
}
#[inline]
pub fn patch<H: Handler>(self, goal: H) -> Self {
self.push(Router::with_filter(filters::patch()).goal(goal))
}
#[inline]
pub fn head<H: Handler>(self, goal: H) -> Self {
self.push(Router::with_filter(filters::head()).goal(goal))
}
#[inline]
pub fn options<H: Handler>(self, goal: H) -> Self {
self.push(Router::with_filter(filters::options()).goal(goal))
}
}
const SYMBOL_DOWN: &str = "│";
const SYMBOL_TEE: &str = "├";
const SYMBOL_ELL: &str = "└";
const SYMBOL_RIGHT: &str = "─";
impl fmt::Debug for Router {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
fn print(f: &mut Formatter, prefix: &str, last: bool, router: &Router) -> fmt::Result {
let mut path = "".to_owned();
let mut others = Vec::with_capacity(router.filters.len());
if router.filters.is_empty() {
"!NULL!".clone_into(&mut path);
} else {
for filter in &router.filters {
let info = format!("{filter:?}");
if info.starts_with("path:") {
info.split_once(':')
.expect("`split_once` get `None`")
.1
.clone_into(&mut path)
} else {
let mut parts = info.splitn(2, ':').collect::<Vec<_>>();
if !parts.is_empty() {
others.push(parts.pop().expect("part should exists.").to_owned());
}
}
}
}
let cp = if last {
format!("{prefix}{SYMBOL_ELL}{SYMBOL_RIGHT}{SYMBOL_RIGHT}")
} else {
format!("{prefix}{SYMBOL_TEE}{SYMBOL_RIGHT}{SYMBOL_RIGHT}")
};
let hd = if let Some(goal) = &router.goal {
format!(" -> {}", goal.type_name())
} else {
"".into()
};
if !others.is_empty() {
writeln!(f, "{cp}{path}[{}]{hd}", others.join(","))?;
} else {
writeln!(f, "{cp}{path}{hd}")?;
}
let routers = router.routers();
if !routers.is_empty() {
let np = if last {
format!("{prefix} ")
} else {
format!("{prefix}{SYMBOL_DOWN} ")
};
for (i, router) in routers.iter().enumerate() {
print(f, &np, i == routers.len() - 1, router)?;
}
}
Ok(())
}
print(f, "", true, self)
}
}
#[cfg(test)]
mod tests {
use super::{PathState, Router};
use crate::handler;
use crate::test::TestClient;
use crate::Response;
#[handler]
async fn fake_handler(_res: &mut Response) {}
#[test]
fn test_router_debug() {
let router = Router::default()
.push(
Router::with_path("users")
.push(Router::with_path("<id>").push(Router::with_path("emails").get(fake_handler)))
.push(
Router::with_path("<id>/articles/<aid>")
.get(fake_handler)
.delete(fake_handler),
),
)
.push(
Router::with_path("articles")
.push(
Router::with_path("<id>/authors/<aid>")
.get(fake_handler)
.delete(fake_handler),
)
.push(Router::with_path("<id>").get(fake_handler).delete(fake_handler)),
);
assert_eq!(
format!("{:?}", router),
r#"└──!NULL!
├──users
│ ├──<id>
│ │ └──emails
│ │ └──[GET] -> salvo_core::routing::router::tests::fake_handler
│ └──<id>/articles/<aid>
│ ├──[GET] -> salvo_core::routing::router::tests::fake_handler
│ └──[DELETE] -> salvo_core::routing::router::tests::fake_handler
└──articles
├──<id>/authors/<aid>
│ ├──[GET] -> salvo_core::routing::router::tests::fake_handler
│ └──[DELETE] -> salvo_core::routing::router::tests::fake_handler
└──<id>
├──[GET] -> salvo_core::routing::router::tests::fake_handler
└──[DELETE] -> salvo_core::routing::router::tests::fake_handler
"#
);
}
#[test]
fn test_router_detect1() {
let router = Router::default().push(
Router::with_path("users")
.push(Router::with_path("<id>").push(Router::with_path("emails").get(fake_handler))),
);
let mut req = TestClient::get("http://local.host/users/12/emails").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect2() {
let router = Router::new()
.push(Router::with_path("users").push(Router::with_path("<id>").get(fake_handler)))
.push(
Router::with_path("users")
.push(Router::with_path("<id>").push(Router::with_path("emails").get(fake_handler))),
);
let mut req = TestClient::get("http://local.host/users/12/emails").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect3() {
let router = Router::new().push(
Router::with_path("users").push(
Router::with_path(r"<id:/\d+/>")
.push(Router::new().push(Router::with_path("facebook/insights/<**rest>").goal(fake_handler))),
),
);
let mut req = TestClient::get("http://local.host/users/12/facebook/insights").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
let mut req = TestClient::get("http://local.host/users/12/facebook/insights/23").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect4() {
let router = Router::new().push(
Router::with_path("users").push(
Router::with_path(r"<id:/\d+/>")
.push(Router::new().push(Router::with_path("facebook/insights/<*+rest>").goal(fake_handler))),
),
);
let mut req = TestClient::get("http://local.host/users/12/facebook/insights").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_none());
let mut req = TestClient::get("http://local.host/users/12/facebook/insights/23").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect5() {
let router =
Router::new().push(Router::with_path("users").push(Router::with_path(r"<id:/\d+/>").push(
Router::new().push(
Router::with_path("facebook/insights").push(Router::with_path("<**rest>").goal(fake_handler)),
),
)));
let mut req = TestClient::get("http://local.host/users/12/facebook/insights").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
let mut req = TestClient::get("http://local.host/users/12/facebook/insights/23").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
assert_eq!(path_state.params["id"], "12");
}
#[test]
fn test_router_detect6() {
let router =
Router::new().push(Router::with_path("users").push(Router::with_path(r"<id:/\d+/>").push(
Router::new().push(
Router::with_path("facebook/insights").push(Router::new().path("<*+rest>").goal(fake_handler)),
),
)));
let mut req = TestClient::get("http://local.host/users/12/facebook/insights").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_none());
let mut req = TestClient::get("http://local.host/users/12/facebook/insights/23").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect_utf8() {
let router =
Router::new().push(Router::with_path("用户").push(Router::with_path(r"<id:/\d+/>").push(
Router::new().push(
Router::with_path("facebook/insights").push(Router::with_path("<*+rest>").goal(fake_handler)),
),
)));
let mut req = TestClient::get("http://local.host/%E7%94%A8%E6%88%B7/12/facebook/insights").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_none());
let mut req = TestClient::get("http://local.host/%E7%94%A8%E6%88%B7/12/facebook/insights/23").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect9() {
let router = Router::new().push(Router::with_path("users/<sub:/(images|css)/>/<filename>").goal(fake_handler));
let mut req = TestClient::get("http://local.host/users/12/m.jpg").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_none());
let mut req = TestClient::get("http://local.host/users/css/m.jpg").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect10() {
let router = Router::new().push(Router::with_path(r"users/<*sub:/(images|css)/.+/>").goal(fake_handler));
let mut req = TestClient::get("http://local.host/users/12/m.jpg").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_none());
let mut req = TestClient::get("http://local.host/users/css/abc/m.jpg").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect11() {
let router =
Router::new().push(Router::with_path(r"avatars/<width:/\d+/>x<height:/\d+/>.<ext>").goal(fake_handler));
let mut req = TestClient::get("http://local.host/avatars/321x641f.webp").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_none());
let mut req = TestClient::get("http://local.host/avatars/320x640.webp").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect12() {
let router = Router::new().push(Router::with_path("/.well-known/acme-challenge/<token>").goal(fake_handler));
let mut req = TestClient::get("http://local.host/.well-known/acme-challenge/q1XXrxIx79uXNl3I").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
}
#[test]
fn test_router_detect13() {
let router = Router::new()
.path("user/<id:/[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}/>")
.get(fake_handler);
let mut req = TestClient::get("http://local.host/user/726d694c-7af0-4bb0-9d22-706f7e38641e").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
let mut req = TestClient::get("http://local.host/user/726d694c-7af0-4bb0-9d22-706f7e386e").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_none());
}
#[test]
fn test_router_detect_path_encoded() {
let router = Router::new().path("api/<p>").get(fake_handler);
let mut req = TestClient::get("http://127.0.0.1:6060/api/a%2fb%2fc").build();
let mut path_state = PathState::new(req.uri().path());
let matched = router.detect(&mut req, &mut path_state);
assert!(matched.is_some());
assert_eq!(path_state.params["p"], "a/b/c");
}
}