use std::collections::hash_map::{Entry, HashMap};
use crate::{MediaType, Method, Request, Response, StatusCode, Version};
pub use crate::common::RouteError;
pub trait EndpointHandler<T>: Sync + Send {
fn handle_request(&self, req: &Request, arg: &T) -> Response;
}
pub struct HttpRoutes<T> {
server_id: String,
prefix: String,
media_type: MediaType,
routes: HashMap<String, Box<dyn EndpointHandler<T> + Sync + Send>>,
}
impl<T: Send> HttpRoutes<T> {
pub fn new(server_id: String, prefix: String) -> Self {
HttpRoutes {
server_id,
prefix,
media_type: MediaType::ApplicationJson,
routes: HashMap::new(),
}
}
pub fn add_route(
&mut self,
method: Method,
path: String,
handler: Box<dyn EndpointHandler<T> + Sync + Send>,
) -> Result<(), RouteError> {
let full_path = format!("{}:{}{}", method.to_str(), self.prefix, path);
match self.routes.entry(full_path.clone()) {
Entry::Occupied(_) => Err(RouteError::HandlerExist(full_path)),
Entry::Vacant(entry) => {
entry.insert(handler);
Ok(())
}
}
}
pub fn handle_http_request(&self, request: &Request, argument: &T) -> Response {
let path = format!(
"{}:{}",
request.method().to_str(),
request.uri().get_abs_path()
);
let mut response = match self.routes.get(&path) {
Some(route) => route.handle_request(request, argument),
None => Response::new(Version::Http11, StatusCode::NotFound),
};
response.set_server(&self.server_id);
response.set_content_type(self.media_type);
response
}
}
#[cfg(test)]
mod tests {
use super::*;
struct HandlerArg(bool);
struct MockHandler {}
impl EndpointHandler<HandlerArg> for MockHandler {
fn handle_request(&self, _req: &Request, _arg: &HandlerArg) -> Response {
Response::new(Version::Http11, StatusCode::OK)
}
}
#[test]
fn test_create_router() {
let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string());
let handler = MockHandler {};
let res = router.add_route(Method::Get, "/func1".to_string(), Box::new(handler));
assert!(res.is_ok());
assert!(router.routes.contains_key("GET:/api/v1/func1"));
let handler = MockHandler {};
match router.add_route(Method::Get, "/func1".to_string(), Box::new(handler)) {
Err(RouteError::HandlerExist(_)) => {}
_ => panic!("add_route() should return error for path with existing handler"),
}
let handler = MockHandler {};
let res = router.add_route(Method::Put, "/func1".to_string(), Box::new(handler));
assert!(res.is_ok());
let handler = MockHandler {};
let res = router.add_route(Method::Get, "/func2".to_string(), Box::new(handler));
assert!(res.is_ok());
}
#[test]
fn test_handle_http_request() {
let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string());
let handler = MockHandler {};
router
.add_route(Method::Get, "/func1".to_string(), Box::new(handler))
.unwrap();
let request =
Request::try_from(b"GET http://localhost/api/v1/func2 HTTP/1.1\r\n\r\n", None).unwrap();
let arg = HandlerArg(true);
let reply = router.handle_http_request(&request, &arg);
assert_eq!(reply.status(), StatusCode::NotFound);
}
}