use super::ServerRouter;
use super::types::RouteMatch;
use hyper::Method;
use reinhardt_di::InjectionContext;
use reinhardt_http::PathParams;
use reinhardt_middleware::Middleware;
use std::borrow::Cow;
use std::sync::{Arc, PoisonError};
impl ServerRouter {
pub(crate) fn strip_prefix_normalized<'a>(prefix: &str, path: &'a str) -> Option<Cow<'a, str>> {
if prefix.is_empty() {
return Some(Cow::Borrowed(path));
}
let stripped = path.strip_prefix(prefix)?;
if !prefix.ends_with('/') && !stripped.is_empty() && !stripped.starts_with('/') {
return None;
}
Some(if stripped.is_empty() {
Cow::Borrowed("/")
} else if stripped.starts_with('/') {
Cow::Borrowed(stripped)
} else {
Cow::Owned(format!("/{stripped}"))
})
}
pub(crate) fn resolve(&self, path: &str, method: &Method) -> Option<RouteMatch> {
let remaining_path = Self::strip_prefix_normalized(&self.prefix, path)?;
let own_middleware = self.build_middleware_with_exclusions();
for child in &self.children {
if let Some(route_match) =
child.resolve_internal(&remaining_path, method, &own_middleware, &self.di_context)
{
return Some(route_match);
}
}
self.match_own_routes_with_context(
&remaining_path,
method,
own_middleware,
self.di_context.clone(),
)
}
pub(crate) fn resolve_internal(
&self,
path: &str,
method: &Method,
parent_middleware: &[Arc<dyn Middleware>],
parent_di: &Option<Arc<InjectionContext>>,
) -> Option<RouteMatch> {
let remaining_path = Self::strip_prefix_normalized(&self.prefix, path)?;
let mut middleware_stack = parent_middleware.to_vec();
middleware_stack.extend(self.build_middleware_with_exclusions());
let di_context = self.di_context.clone().or_else(|| parent_di.clone());
for child in &self.children {
if let Some(route_match) =
child.resolve_internal(&remaining_path, method, &middleware_stack, &di_context)
{
return Some(route_match);
}
}
self.match_own_routes_with_context(&remaining_path, method, middleware_stack, di_context)
}
#[cfg(test)]
pub(crate) fn match_own_routes(&self, path: &str, method: &Method) -> Option<RouteMatch> {
self.match_own_routes_with_context(
path,
method,
self.build_middleware_with_exclusions(),
self.di_context.clone(),
)
}
pub(crate) fn match_own_routes_with_context(
&self,
path: &str,
method: &Method,
middleware_stack: Vec<Arc<dyn Middleware>>,
di_context: Option<Arc<InjectionContext>>,
) -> Option<RouteMatch> {
self.compile_routes();
let search_path: Cow<'_, str> = if path.starts_with('/') {
Cow::Borrowed(path)
} else {
Cow::Owned(format!("/{path}"))
};
let router_lock = match *method {
Method::GET => &self.get_router,
Method::POST => &self.post_router,
Method::PUT => &self.put_router,
Method::DELETE => &self.delete_router,
Method::PATCH => &self.patch_router,
Method::HEAD => &self.head_router,
Method::OPTIONS => &self.options_router,
_ => &self.get_router,
};
let router = router_lock.read().unwrap_or_else(PoisonError::into_inner);
macro_rules! return_route_match {
($matched:expr) => {{
let matched = $matched;
let route_handler = matched.value;
let mut params = PathParams::with_capacity(matched.params.iter().count());
for (key, value) in matched.params.iter() {
params.insert(key, value);
}
let mut combined_middleware = middleware_stack;
combined_middleware.extend(route_handler.middleware.iter().cloned());
return Some(RouteMatch {
handler: route_handler.handler.clone(),
params,
middleware_stack: combined_middleware,
di_context,
});
}};
}
if let Ok(matched) = router.at(search_path.as_ref()) {
return_route_match!(matched);
}
if search_path.as_ref().ends_with('/') {
let without_slash = search_path.as_ref().trim_end_matches('/');
let fallback_path = if without_slash.is_empty() {
"/"
} else {
without_slash
};
if fallback_path != search_path.as_ref()
&& let Ok(matched) = router.at(fallback_path)
{
return_route_match!(matched);
}
} else {
let fallback_path = format!("{}/", search_path.as_ref());
if let Ok(matched) = router.at(&fallback_path) {
return_route_match!(matched);
}
}
None
}
pub(crate) fn path_exists_for_any_method(&self, path: &str) -> bool {
self.compile_routes();
let search_path = match Self::strip_prefix_normalized(&self.prefix, path) {
Some(p) => p,
None => return false,
};
let method_routers = [
&self.get_router,
&self.post_router,
&self.put_router,
&self.delete_router,
&self.patch_router,
&self.head_router,
&self.options_router,
];
let path_exists = |candidate_path: &str| {
for router_lock in method_routers {
let router = router_lock.read().unwrap_or_else(PoisonError::into_inner);
if router.at(candidate_path).is_ok() {
return true;
}
}
for child in &self.children {
if child.path_exists_for_any_method(candidate_path) {
return true;
}
}
false
};
if path_exists(search_path.as_ref()) {
return true;
}
if search_path.as_ref().ends_with('/') {
let without_slash = search_path.as_ref().trim_end_matches('/');
let fallback_path = if without_slash.is_empty() {
"/"
} else {
without_slash
};
if fallback_path != search_path.as_ref() {
return path_exists(fallback_path);
}
} else {
let fallback_path = format!("{}/", search_path.as_ref());
return path_exists(&fallback_path);
}
false
}
}