use crate::http::{error_response, Error, Request, Response};
use http::{Method, StatusCode};
use matchit::Router;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
pub trait Handler: Send + Sync + 'static {
fn call(&self, req: Request) -> Result<Response, Error>;
}
impl Debug for dyn Handler {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("dyn Handler").finish()
}
}
impl<F> Handler for F
where
F: Send + Sync + 'static + Fn(Request) -> Result<Response, Error>,
{
fn call<'a>(&'_ self, req: Request) -> Result<Response, Error> {
(self)(req)
}
}
type RouteHandler = HashMap<Method, Router<Arc<dyn Handler>>>;
lazy_static::lazy_static! {
static ref ROUTER: Mutex<RouteHandler> = Mutex::new(HashMap::new());
}
macro_rules! method_route {
($method:ident) => {
pub fn $method(
path: &str,
handler: impl Handler,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let method_name = Method::from_str(&stringify!($method).to_uppercase())?;
ROUTER
.lock()
.unwrap()
.entry(method_name)
.or_default()
.insert(path, Arc::new(handler))?;
Ok(())
}
};
}
method_route!(post);
method_route!(get);
method_route!(put);
method_route!(delete);
method_route!(head);
method_route!(options);
method_route!(patch);
pub fn any(
path: &str,
handler: impl Handler + std::marker::Copy,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
get(path, handler)?;
post(path, handler)?;
put(path, handler)?;
delete(path, handler)?;
head(path, handler)?;
options(path, handler)?;
patch(path, handler)?;
Ok(())
}
pub fn route(mut req: Request) -> Result<Response, Error> {
let method = req.method().clone();
let path = req.uri().clone();
let path = path.path();
let mut router = ROUTER.lock().unwrap();
let router = router.entry(method).or_default();
let matched = router.at(path);
if matched.is_err() {
return Ok(error_response(
StatusCode::NOT_FOUND,
"route mismatch".to_string(),
));
}
let mut route_params = HashMap::new();
matched.as_ref().unwrap().params.iter().for_each(|(k, v)| {
route_params.insert(k.to_string(), v.to_string());
});
req.extensions_mut().insert(route_params);
let handler = matched.unwrap().value;
handler.call(req)
}
pub fn params(req: &Request, key: String) -> Option<String> {
let ext = req.extensions();
let params = ext.get::<HashMap<String, String>>()?;
let value = params.get(&key)?;
Some(value.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::http::{Body, Error, Request, Response};
#[test]
fn test_handler_impl() {
let handler: Arc<dyn Handler> =
Arc::new(|_req: Request| Ok(Response::new(Body::from_handle(2))));
let req = http::Request::builder()
.uri("/")
.body(Body::from_handle(1))
.unwrap();
let resp = handler.call(req).unwrap();
assert_eq!(resp.status(), 200);
assert_eq!(resp.body().body_handle(), 2);
}
pub fn test_route_1(req: Request) -> Result<Response, Error> {
let url = req.uri().clone();
let method = req.method().to_string().to_uppercase();
Ok(http::Response::builder()
.status(200)
.header("X-Request-Url", url.to_string())
.header("X-Request-Method", method)
.body(Body::from_handle(2))
.unwrap())
}
#[test]
fn test_router() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
{
get("/abc", test_route_1)?;
}
{
let mut router = ROUTER.lock().unwrap();
let router = router.entry(Method::GET).or_default();
let matched = router.at("/abcd");
if matched.is_err() {
assert_eq!(matched.err().unwrap(), matchit::MatchError::NotFound);
}
}
{
let mut router = ROUTER.lock().unwrap();
let router = router.entry(Method::GET).or_default();
let matched = router.at("/abc");
assert!(matched.is_ok());
let handler = matched.unwrap();
let req = http::Request::builder()
.method(Method::GET)
.uri("/abc")
.body(Body::from_handle(1))
.unwrap();
let resp = handler.value.call(req).unwrap();
assert_eq!(resp.status(), 200);
}
Ok(())
}
#[test]
fn test_wildcard() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
{
any("/xyz/*path", test_route_1)?;
}
{
let mut router = ROUTER.lock().unwrap();
let router = router.entry(Method::GET).or_default();
let matched = router.at("/xyz/abc");
assert!(matched.is_ok());
let mut route_params = HashMap::new();
matched.as_ref().unwrap().params.iter().for_each(|(k, v)| {
route_params.insert(k.to_string(), v.to_string());
});
let path_str = matched.as_ref().unwrap().params.get("path").unwrap();
assert_eq!(path_str, "abc");
let handler = matched.unwrap();
let mut req = http::Request::builder().method(Method::GET).uri("/xyz/abc");
req.extensions_mut().unwrap().insert(route_params);
let req = req.body(Body::from_handle(1)).unwrap();
let p = params(&req, "path".to_string());
assert_eq!(p, Some("abc".to_string()));
let resp = handler.value.call(req).unwrap();
assert_eq!(resp.status(), 200);
}
Ok(())
}
}