use crate::app::HandlerFn;
use crate::request::Request;
use crate::response::Response;
use hyper::Method;
use matchit::Router as MatchitRouter;
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
pub struct RouteHandler {
handler: HandlerFn,
}
pub struct Router {
routes: HashMap<Method, MatchitRouter<RouteHandler>>,
prefix: String,
}
impl Router {
pub fn new() -> Self {
Self {
routes: HashMap::new(),
prefix: String::new(),
}
}
pub fn with_prefix(prefix: &str) -> Self {
Self {
routes: HashMap::new(),
prefix: prefix.to_string(),
}
}
pub fn add_route(&mut self, method: Method, path: &str, handler: HandlerFn) {
let full_path = format!("{}{}", self.prefix, path);
let router = self.routes.entry(method).or_insert_with(MatchitRouter::new);
let converted_path = convert_express_params(&full_path);
if let Err(e) = router.insert(&converted_path, RouteHandler { handler }) {
tracing::warn!("Failed to insert route {}: {:?}", converted_path, e);
}
}
pub fn find_route(
&self,
method: &Method,
path: &str,
) -> Option<(&HandlerFn, HashMap<String, String>)> {
if let Some(router) = self.routes.get(method) {
if let Ok(matched) = router.at(path) {
let params: HashMap<String, String> = matched
.params
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
return Some((&matched.value.handler, params));
}
}
None
}
pub fn mount(&mut self, _prefix: &str, mut other: Router) {
for (method, other_router) in other.routes.drain() {
self.routes.entry(method).or_insert(other_router);
}
}
pub fn get<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request, Response) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Response> + Send + 'static,
{
self.add_route(
Method::GET,
path,
Arc::new(move |req, res| Box::pin(handler(req, res))),
);
self
}
pub fn post<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request, Response) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Response> + Send + 'static,
{
self.add_route(
Method::POST,
path,
Arc::new(move |req, res| Box::pin(handler(req, res))),
);
self
}
pub fn put<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request, Response) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Response> + Send + 'static,
{
self.add_route(
Method::PUT,
path,
Arc::new(move |req, res| Box::pin(handler(req, res))),
);
self
}
pub fn delete<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request, Response) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Response> + Send + 'static,
{
self.add_route(
Method::DELETE,
path,
Arc::new(move |req, res| Box::pin(handler(req, res))),
);
self
}
pub fn patch<F, Fut>(&mut self, path: &str, handler: F) -> &mut Self
where
F: Fn(Request, Response) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Response> + Send + 'static,
{
self.add_route(
Method::PATCH,
path,
Arc::new(move |req, res| Box::pin(handler(req, res))),
);
self
}
pub fn group<F>(&mut self, prefix: &str, configure: F) -> &mut Self
where
F: FnOnce(&mut Router),
{
let mut group_router = Router::with_prefix(&format!("{}{}", self.prefix, prefix));
configure(&mut group_router);
for (method, group_routes) in group_router.routes {
self.routes.entry(method).or_insert(group_routes);
}
self
}
}
impl Default for Router {
fn default() -> Self {
Self::new()
}
}
fn convert_express_params(path: &str) -> String {
let mut result = String::with_capacity(path.len());
let mut chars = path.chars().peekable();
while let Some(c) = chars.next() {
if c == ':' {
result.push('{');
while let Some(&next) = chars.peek() {
if next.is_alphanumeric() || next == '_' {
result.push(chars.next().unwrap());
} else {
break;
}
}
result.push('}');
} else {
result.push(c);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convert_express_params() {
assert_eq!(convert_express_params("/users/:id"), "/users/{id}");
assert_eq!(
convert_express_params("/users/:id/posts/:postId"),
"/users/{id}/posts/{postId}"
);
assert_eq!(convert_express_params("/static"), "/static");
}
}