volga 0.9.1

Easy & Fast Web Framework for Rust
Documentation
use crate::error::Error;
use crate::http::{IntoResponse, endpoints::args::FromRequest};
use crate::{HttpRequest, HttpResult};
use futures_util::future::BoxFuture;
use std::{future::Future, sync::Arc};

/// Represents a specific registered request handler
pub(crate) type RouteHandler = Arc<dyn Handler + Send + Sync>;

pub(crate) trait Handler {
    fn call(&self, req: HttpRequest) -> BoxFuture<'_, HttpResult>;
}

/// Represents a function request handler that could take different arguments
/// that implements [`FromRequest`] trait.
pub(crate) struct Func<F, R, Args>
where
    F: GenericHandler<Args, Output = R>,
    R: IntoResponse,
    Args: FromRequest,
{
    func: F,
    _marker: std::marker::PhantomData<fn(Args)>,
}

impl<F, R, Args> Func<F, R, Args>
where
    F: GenericHandler<Args, Output = R>,
    R: IntoResponse,
    Args: FromRequest,
{
    /// Creates a new [`Func`] wrapped into [`Arc`]
    #[inline]
    pub(crate) fn new(func: F) -> Arc<Self> {
        Arc::new(Self::new_local(func))
    }

    /// Creates a new [`Func`]
    #[inline]
    pub(crate) fn new_local(func: F) -> Self {
        Self {
            func,
            _marker: std::marker::PhantomData,
        }
    }
}

impl<F, R, Args> Handler for Func<F, R, Args>
where
    F: GenericHandler<Args, Output = R>,
    R: IntoResponse,
    Args: FromRequest + Send,
{
    #[inline]
    fn call(&self, req: HttpRequest) -> BoxFuture<'_, HttpResult> {
        Box::pin(async move {
            let args = Args::from_request(req).await?;
            self.func.call(args).await.into_response()
        })
    }
}

/// Describes a generic request handler that could take 0 or N parameters of types
/// that are implement [`FromPayload`] trait
pub trait GenericHandler<Args>: Clone + Send + Sync + 'static {
    /// Return type
    type Output;

    /// Calls a generic handler
    fn call(&self, args: Args) -> impl Future<Output = Self::Output> + Send;
}

/// Describes a generic [`map_err`] middleware handler that could take 0 or N parameters and [`Error`]
pub trait MapErr<Args>: Clone + Send + Sync + 'static {
    /// Return type
    type Output;

    /// Calls an error handler
    fn map_err(&self, err: Error, args: Args) -> impl Future<Output = Self::Output> + Send;
}

macro_rules! define_generic_handler ({ $($param:ident)* } => {
    impl<Func, Fut: Send, $($param,)*> GenericHandler<($($param,)*)> for Func
    where
        Func: Fn($($param),*) -> Fut + Send + Sync + Clone + 'static,
        Fut: Future,
    {
        type Output = Fut::Output;

        #[inline]
        #[allow(non_snake_case)]
        fn call(&self, ($($param,)*): ($($param,)*)) -> impl Future<Output = Self::Output> + Send {
            (self)($($param,)*)
        }
    }
    impl<Func, Fut: Send, $($param,)*> MapErr<($($param,)*)> for Func
    where
        Func: Fn(Error, $($param,)*) -> Fut + Send + Sync + Clone + 'static,
        Fut: Future,
    {
        type Output = Fut::Output;

        #[inline]
        #[allow(non_snake_case)]
        fn map_err(&self, err: Error, ($($param,)*): ($($param,)*)) -> impl Future<Output = Self::Output> {
            (self)(err, $($param,)*)
        }
    }
});

define_generic_handler! {}
define_generic_handler! { T1 }
define_generic_handler! { T1 T2 }
define_generic_handler! { T1 T2 T3 }
define_generic_handler! { T1 T2 T3 T4 }
define_generic_handler! { T1 T2 T3 T4 T5 }
define_generic_handler! { T1 T2 T3 T4 T5 T6 }
define_generic_handler! { T1 T2 T3 T4 T5 T6 T7 }
define_generic_handler! { T1 T2 T3 T4 T5 T6 T7 T8 }
define_generic_handler! { T1 T2 T3 T4 T5 T6 T7 T8 T9 }
define_generic_handler! { T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 }

#[cfg(test)]
mod tests {
    use super::{GenericHandler, MapErr};
    use crate::error::Error;

    #[tokio::test]
    async fn generic_handler_invokes_function_with_arguments() {
        let handler = |a: i32, b: i32| async move { a + b };
        let result = GenericHandler::call(&handler, (2, 3)).await;

        assert_eq!(result, 5);
    }

    #[tokio::test]
    async fn map_err_handler_invokes_function_with_error_and_args() {
        let handler = |err: Error, code: u16| async move { (err.status.as_u16(), code) };
        let err = Error::client_error("bad");

        let result = MapErr::map_err(&handler, err, (42,)).await;
        assert_eq!(result, (400, 42));
    }
}