use std::fmt::{self, Debug, Formatter};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use super::filters::{self, FnFilter, PathFilter};
use super::{DetectMatched, Filter, 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<Self>,
pub filters: Vec<Box<dyn Filter>>,
pub hoops: Vec<Arc<dyn Handler>>,
pub goal: Option<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]
#[must_use]
pub fn routers(&self) -> &Vec<Self> {
&self.routers
}
#[inline]
pub fn routers_mut(&mut self) -> &mut Vec<Self> {
&mut self.routers
}
#[inline]
#[must_use]
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]
#[must_use]
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 async fn detect(
&self,
req: &mut Request,
path_state: &mut PathState,
) -> Option<DetectMatched> {
Box::pin(async move {
for filter in &self.filters {
if !filter.filter(req, path_state).await {
return None;
}
}
if !self.routers.is_empty() {
let original_cursor = path_state.cursor;
#[cfg(feature = "matched-path")]
let original_matched_parts_len = path_state.matched_parts.len();
for child in &self.routers {
if let Some(dm) = child.detect(req, path_state).await {
let mut hoops = Vec::with_capacity(self.hoops.len() + dm.hoops.len());
hoops.extend_from_slice(&self.hoops);
hoops.extend_from_slice(&dm.hoops);
return Some(DetectMatched {
hoops,
goal: dm.goal.clone(),
});
} else {
#[cfg(feature = "matched-path")]
path_state
.matched_parts
.truncate(original_matched_parts_len);
path_state.cursor = original_cursor;
}
}
}
if path_state.is_ended() {
path_state.once_ended = true;
if let Some(goal) = &self.goal {
return Some(DetectMatched {
hoops: self.hoops.clone(),
goal: goal.clone(),
});
}
}
None
})
.await
}
#[inline]
#[must_use]
pub fn unshift(mut self, router: Self) -> Self {
self.routers.insert(0, router);
self
}
#[inline]
#[must_use]
pub fn insert(mut self, index: usize, router: Self) -> Self {
self.routers.insert(index, router);
self
}
#[inline]
#[must_use]
pub fn push(mut self, router: Self) -> Self {
self.routers.push(router);
self
}
#[inline]
#[must_use]
pub fn append(mut self, others: &mut Vec<Self>) -> Self {
self.routers.append(others);
self
}
#[inline]
#[must_use]
pub fn with_hoop<H: Handler>(hoop: H) -> Self {
Self::new().hoop(hoop)
}
#[inline]
#[must_use]
pub fn with_hoop_when<H, F>(hoop: H, filter: F) -> Self
where
H: Handler,
F: Fn(&Request, &Depot) -> bool + Send + Sync + 'static,
{
Self::new().hoop_when(hoop, filter)
}
#[inline]
#[must_use]
pub fn hoop<H: Handler>(mut self, hoop: H) -> Self {
self.hoops.push(Arc::new(hoop));
self
}
#[inline]
#[must_use]
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]
#[must_use]
pub fn with_path(path: impl Into<String>) -> Self {
Self::with_filter(PathFilter::new(path))
}
#[inline]
#[must_use]
pub fn path(self, path: impl Into<String>) -> Self {
self.filter(PathFilter::new(path))
}
#[inline]
#[must_use]
pub fn with_filter(filter: impl Filter + Sized) -> Self {
Self::new().filter(filter)
}
#[inline]
#[must_use]
pub fn filter(mut self, filter: impl Filter + Sized) -> Self {
self.filters.push(Box::new(filter));
self
}
#[inline]
#[must_use]
pub fn with_filter_fn<T>(func: T) -> Self
where
T: Fn(&mut Request, &mut PathState) -> bool + Send + Sync + 'static,
{
Self::with_filter(FnFilter(func))
}
#[inline]
#[must_use]
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]
#[must_use]
pub fn goal<H: Handler>(mut self, goal: H) -> Self {
self.goal = Some(Arc::new(goal));
self
}
#[inline]
#[must_use]
pub fn then<F>(self, func: F) -> Self
where
F: FnOnce(Self) -> Self,
{
func(self)
}
#[inline]
#[must_use]
pub fn scheme(self, scheme: Scheme) -> Self {
self.filter(filters::scheme(scheme))
}
#[inline]
#[must_use]
pub fn host(self, host: impl Into<String>) -> Self {
self.filter(filters::host(host))
}
#[inline]
#[must_use]
pub fn with_host(host: impl Into<String>) -> Self {
Self::with_filter(filters::host(host))
}
#[inline]
#[must_use]
pub fn port(self, port: u16) -> Self {
self.filter(filters::port(port))
}
#[inline]
#[must_use]
pub fn with_port(port: u16) -> Self {
Self::with_filter(filters::port(port))
}
#[inline]
#[must_use]
pub fn get<H: Handler>(self, goal: H) -> Self {
self.push(Self::with_filter(filters::get()).goal(goal))
}
#[inline]
#[must_use]
pub fn post<H: Handler>(self, goal: H) -> Self {
self.push(Self::with_filter(filters::post()).goal(goal))
}
#[inline]
#[must_use]
pub fn put<H: Handler>(self, goal: H) -> Self {
self.push(Self::with_filter(filters::put()).goal(goal))
}
#[inline]
#[must_use]
pub fn delete<H: Handler>(self, goal: H) -> Self {
self.push(Self::with_filter(filters::delete()).goal(goal))
}
#[inline]
#[must_use]
pub fn patch<H: Handler>(self, goal: H) -> Self {
self.push(Self::with_filter(filters::patch()).goal(goal))
}
#[inline]
#[must_use]
pub fn head<H: Handler>(self, goal: H) -> Self {
self.push(Self::with_filter(filters::head()).goal(goal))
}
#[inline]
#[must_use]
pub fn options<H: Handler>(self, goal: H) -> Self {
self.push(Self::with_filter(filters::options()).goal(goal))
}
}
const SYMBOL_DOWN: &str = "│";
const SYMBOL_TEE: &str = "├";
const SYMBOL_ELL: &str = "â””";
const SYMBOL_RIGHT: &str = "─";
impl 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 = router
.goal
.as_ref()
.map(|goal| format!(" -> {}", goal.type_name()))
.unwrap_or_default();
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::test::TestClient;
use crate::{Response, handler};
#[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
"#
);
}
#[tokio::test]
async 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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_some());
assert_eq!(path_state.params["id"], "12");
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
assert!(matched.is_some());
}
#[tokio::test]
async 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).await;
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).await;
assert!(matched.is_none());
}
#[tokio::test]
async 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).await;
assert!(matched.is_some());
assert_eq!(path_state.params["p"], "a/b/c");
}
}