use super::error::RouterError;
use super::from_request::{FromRequest, RouteContext};
use super::params::{FromPath, ParamContext, Path, SingleFromPath};
use reinhardt_core::types::page::Page;
use std::marker::PhantomData;
use std::sync::Arc;
pub trait RouteHandler: Send + Sync {
fn handle(&self, ctx: &ParamContext) -> Result<Page, RouterError>;
}
pub(crate) struct NoParamsHandler<F> {
handler: F,
}
impl<F> NoParamsHandler<F> {
pub(crate) fn new(handler: F) -> Self {
Self { handler }
}
}
unsafe impl<F: Send> Send for NoParamsHandler<F> {}
unsafe impl<F: Sync> Sync for NoParamsHandler<F> {}
impl<F> RouteHandler for NoParamsHandler<F>
where
F: Fn() -> Page + Send + Sync,
{
fn handle(&self, _ctx: &ParamContext) -> Result<Page, RouterError> {
Ok((self.handler)())
}
}
pub(crate) struct WithParamsHandler<F, T> {
handler: F,
_phantom: PhantomData<T>,
}
impl<F, T> WithParamsHandler<F, T> {
pub(crate) fn new(handler: F) -> Self {
Self {
handler,
_phantom: PhantomData,
}
}
}
unsafe impl<F: Send, T> Send for WithParamsHandler<F, T> {}
unsafe impl<F: Sync, T> Sync for WithParamsHandler<F, T> {}
impl<F, T> RouteHandler for WithParamsHandler<F, T>
where
F: Fn(Path<T>) -> Page + Send + Sync,
T: FromPath + Send + Sync,
{
fn handle(&self, ctx: &ParamContext) -> Result<Page, RouterError> {
let params = Path::<T>::from_path(ctx).map_err(RouterError::PathExtraction)?;
Ok((self.handler)(params))
}
}
pub(crate) struct ResultHandler<F, T, E> {
handler: F,
_phantom: PhantomData<(T, E)>,
}
impl<F, T, E> ResultHandler<F, T, E> {
pub(crate) fn new(handler: F) -> Self {
Self {
handler,
_phantom: PhantomData,
}
}
}
unsafe impl<F: Send, T, E> Send for ResultHandler<F, T, E> {}
unsafe impl<F: Sync, T, E> Sync for ResultHandler<F, T, E> {}
impl<F, T, E> RouteHandler for ResultHandler<F, T, E>
where
F: Fn(Path<T>) -> Result<Page, E> + Send + Sync,
T: FromPath + Send + Sync,
E: Into<RouterError> + Send + Sync,
{
fn handle(&self, ctx: &ParamContext) -> Result<Page, RouterError> {
let params = Path::<T>::from_path(ctx).map_err(RouterError::PathExtraction)?;
(self.handler)(params).map_err(|e| e.into())
}
}
pub(crate) fn no_params_handler<F>(handler: F) -> Arc<dyn RouteHandler>
where
F: Fn() -> Page + Send + Sync + 'static,
{
Arc::new(NoParamsHandler::new(handler))
}
pub(crate) fn with_params_handler<F, T>(handler: F) -> Arc<dyn RouteHandler>
where
F: Fn(Path<T>) -> Page + Send + Sync + 'static,
T: FromPath + Send + Sync + 'static,
{
Arc::new(WithParamsHandler::new(handler))
}
pub(crate) fn result_handler<F, T, E>(handler: F) -> Arc<dyn RouteHandler>
where
F: Fn(Path<T>) -> Result<Page, E> + Send + Sync + 'static,
T: FromPath + Send + Sync + 'static,
E: Into<RouterError> + Send + Sync + 'static,
{
Arc::new(ResultHandler::new(handler))
}
mod sealed {
pub trait Sealed<Args> {}
}
pub trait Handler<Args>: sealed::Sealed<Args> + Send + Sync + 'static {
fn into_route_handler(self) -> Arc<dyn RouteHandler>;
}
pub(crate) struct PathHandler<F, Args> {
handler: F,
_phantom: PhantomData<fn(Args) -> ()>,
}
unsafe impl<F: Send, Args> Send for PathHandler<F, Args> {}
unsafe impl<F: Sync, Args> Sync for PathHandler<F, Args> {}
macro_rules! impl_handler {
([$($Ti:ident),+], [$($idx:tt),+]) => {
impl<F, $($Ti),+> sealed::Sealed<($($Ti,)+)> for F
where
F: Fn($(Path<$Ti>),+) -> Page + Send + Sync + 'static,
$($Ti: SingleFromPath + Send + Sync + 'static,)+
{}
impl<F, $($Ti),+> Handler<($($Ti,)+)> for F
where
F: Fn($(Path<$Ti>),+) -> Page + Send + Sync + 'static,
$($Ti: SingleFromPath + Send + Sync + 'static,)+
{
fn into_route_handler(self) -> Arc<dyn RouteHandler> {
Arc::new(PathHandler::<F, ($($Ti,)+)> {
handler: self,
_phantom: PhantomData,
})
}
}
impl<F, $($Ti),+> RouteHandler for PathHandler<F, ($($Ti,)+)>
where
F: Fn($(Path<$Ti>),+) -> Page + Send + Sync,
$($Ti: SingleFromPath + Send + Sync,)+
{
#[allow(non_snake_case)] fn handle(&self, ctx: &ParamContext) -> Result<Page, RouterError> {
$(let $Ti = $Ti::from_path_at(ctx, $idx)
.map_err(RouterError::PathExtraction)?;)+
Ok((self.handler)($(Path($Ti)),+))
}
}
};
}
impl_handler!([T1], [0]);
impl_handler!([T1, T2], [0, 1]);
impl_handler!([T1, T2, T3], [0, 1, 2]);
impl_handler!([T1, T2, T3, T4], [0, 1, 2, 3]);
impl_handler!([T1, T2, T3, T4, T5], [0, 1, 2, 3, 4]);
impl_handler!([T1, T2, T3, T4, T5, T6], [0, 1, 2, 3, 4, 5]);
impl_handler!([T1, T2, T3, T4, T5, T6, T7], [0, 1, 2, 3, 4, 5, 6]);
impl_handler!([T1, T2, T3, T4, T5, T6, T7, T8], [0, 1, 2, 3, 4, 5, 6, 7]);
pub(crate) struct FromRequestHandler<F, P> {
handler: F,
pattern: String,
_phantom: PhantomData<P>,
}
impl<F, P> FromRequestHandler<F, P> {
pub(crate) fn new(handler: F, pattern: String) -> Self {
Self {
handler,
pattern,
_phantom: PhantomData,
}
}
}
unsafe impl<F: Send, P> Send for FromRequestHandler<F, P> {}
unsafe impl<F: Sync, P> Sync for FromRequestHandler<F, P> {}
impl<F, P> RouteHandler for FromRequestHandler<F, P>
where
F: Fn(P) -> Page + Send + Sync,
P: FromRequest + Send + Sync,
{
fn handle(&self, ctx: &ParamContext) -> Result<Page, RouterError> {
let route_ctx = RouteContext::new(
String::new(),
ctx.params().clone(),
ctx.query().unwrap_or("").to_string(),
);
match P::from_request(&route_ctx) {
Ok(props) => Ok((self.handler)(props)),
Err(e) => Ok(Page::Text(
format!("route extraction error on `{}`: {e}", self.pattern).into(),
)),
}
}
}
pub(crate) fn from_request_handler<F, P>(handler: F, pattern: String) -> Arc<dyn RouteHandler>
where
F: Fn(P) -> Page + Send + Sync + 'static,
P: FromRequest + Send + Sync + 'static,
{
Arc::new(FromRequestHandler::new(handler, pattern))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::routers::client_router::error::PathError;
use std::collections::HashMap;
fn test_page() -> Page {
Page::Empty
}
fn page_with_text(s: &str) -> Page {
Page::Text(s.to_string().into())
}
#[test]
fn test_no_params_handler() {
let handler = NoParamsHandler::new(test_page);
let ctx = ParamContext::new(HashMap::new(), Vec::new());
let result = handler.handle(&ctx);
assert!(result.is_ok());
}
#[test]
fn test_with_params_handler() {
let handler =
WithParamsHandler::new(|Path(id): Path<i32>| page_with_text(&format!("ID: {}", id)));
let ctx = ParamContext::new(HashMap::new(), vec!["42".to_string()]);
let result = handler.handle(&ctx);
assert!(result.is_ok());
}
#[test]
fn test_with_params_handler_error() {
let handler = WithParamsHandler::new(|Path(_id): Path<i32>| Page::Empty);
let ctx = ParamContext::new(HashMap::new(), vec!["not_a_number".to_string()]);
let result = handler.handle(&ctx);
assert!(result.is_err());
match result {
Err(RouterError::PathExtraction(PathError::ParseError { .. })) => {}
_ => panic!("Expected PathExtraction error"),
}
}
#[test]
fn test_result_handler_ok() {
let handler = ResultHandler::new(|Path(id): Path<i32>| {
if id > 0 {
Ok(page_with_text(&format!("ID: {}", id)))
} else {
Err(RouterError::NotFound("Invalid ID".to_string()))
}
});
let ctx = ParamContext::new(HashMap::new(), vec!["42".to_string()]);
let result = handler.handle(&ctx);
assert!(result.is_ok());
}
#[test]
fn test_result_handler_err() {
let handler = ResultHandler::new(|Path(id): Path<i32>| {
if id > 0 {
Ok(Page::Empty)
} else {
Err(RouterError::NotFound("Invalid ID".to_string()))
}
});
let ctx = ParamContext::new(HashMap::new(), vec!["-1".to_string()]);
let result = handler.handle(&ctx);
assert!(result.is_err());
match result {
Err(RouterError::NotFound(_)) => {}
_ => panic!("Expected NotFound error"),
}
}
#[test]
fn test_with_params_handler_tuple() {
let handler = WithParamsHandler::new(|Path((user_id, post_id)): Path<(i64, i64)>| {
page_with_text(&format!("User: {}, Post: {}", user_id, post_id))
});
let ctx = ParamContext::new(HashMap::new(), vec!["123".to_string(), "456".to_string()]);
let result = handler.handle(&ctx);
assert!(result.is_ok());
}
#[test]
fn test_helper_no_params_handler() {
let handler: Arc<dyn RouteHandler> = no_params_handler(test_page);
let ctx = ParamContext::new(HashMap::new(), Vec::new());
let result = handler.handle(&ctx);
assert!(result.is_ok());
}
#[test]
fn test_helper_with_params_handler() {
let handler: Arc<dyn RouteHandler> =
with_params_handler(|Path(id): Path<i32>| page_with_text(&format!("ID: {}", id)));
let ctx = ParamContext::new(HashMap::new(), vec!["42".to_string()]);
let result = handler.handle(&ctx);
assert!(result.is_ok());
}
#[test]
fn test_helper_result_handler() {
let handler: Arc<dyn RouteHandler> = result_handler(|Path(id): Path<i32>| {
if id > 0 {
Ok(page_with_text(&format!("ID: {}", id)))
} else {
Err(RouterError::NotFound("Invalid ID".to_string()))
}
});
let ctx = ParamContext::new(HashMap::new(), vec!["42".to_string()]);
let result = handler.handle(&ctx);
assert!(result.is_ok());
}
}