intrepid-core 0.1.6

Manage complex async business logic with ease
Documentation
use futures::Stream;

use crate::{Action, ActionContext, BoxedAction, Context, Extractor, Frame, FrameOutbox, Ready};

use super::{ActiveAction, CandidateAction, ReadyAction};

/// An action is an async function that can be turned into an frame handler.
pub trait Handler<Args, State>: Clone {
    /// Action futures must resolve into an Frame of some kind.
    type Future: std::future::Future<Output = crate::Result<Frame>> + Send + 'static;

    /// Invoke the action with a given frame and state, returning the frameual
    /// frame that will be produced by the action.
    fn invoke(&self, frame: impl Into<Frame>, state: State) -> Self::Future;

    /// Poll for the action context. This is normally a service, but in some cases,
    /// it may be a frame handler or a router.
    fn context(&self) -> ActionContext<State>
    where
        State: Send + Sync + 'static,
    {
        ActionContext::<State>::Unit
    }

    /// Create a new [`RequestHandler`] from the action. This is a struct that implements
    /// the axum [`Handler`] trait, and therefore can be used as a route handler.
    ///
    /// [`RequestHandler`]: crate::RequestHandler
    /// [`Handler`]: axum::handler::Handler
    fn http_handler(&self) -> crate::RequestHandler<Self, Args, State>
    where
        Self: Handler<Args, State> + Clone + Send + Sync + 'static,
        Args: Clone + Send + Sync + 'static,
        State: Clone + Send + Sync + 'static,
    {
        crate::RequestHandler::new(self.clone())
    }

    /// Convert this action into a type erased actionable service.
    fn as_into_actionable(&self) -> BoxedAction<State>
    where
        Self: Clone + Send + Sync + 'static,
        Args: Clone + Send + Sync + 'static,
        State: Clone + Send + Sync + 'static,
    {
        Box::new(self.clone().candidate())
    }

    /// Use `into_stream` to turn the action into a stream and a stream handle. See
    /// [`Actionable::into_stream`] for more information.
    ///
    fn into_stream(self, state: State) -> (impl Stream<Item = crate::Result<Frame>>, FrameOutbox)
    where
        Self: Clone + Send + Sync + 'static,
        Args: Clone + Send + Sync + 'static,
        State: Clone + Send + Sync + 'static,
    {
        self.active(state).into_stream()
    }

    /// Use `with_state` to turn the action into a stateful action, which can be used
    /// as a [`tower::Service`].
    fn ready(self, state: State) -> ReadyAction<Self, Args, State>
    where
        Self: Clone + Send + 'static,
        Args: Clone + Send + 'static,
        State: Clone + Send + 'static,
    {
        Ready::new(self, state).into()
    }

    /// Create a Candidate actionable from the action, with the given state. Candidates
    /// can be "suspended" by boxing them for later.
    fn candidate(self) -> CandidateAction<Self, Args, State>
    where
        Self: Clone + Send + 'static,
        Args: Clone + Send + 'static,
        State: Clone + Send + 'static,
    {
        Action::new(self.clone())
    }

    /// Create an Active actionable from the action, with the given state.
    fn active(self, state: State) -> ActiveAction
    where
        Self: Clone + Send + Sync + 'static,
        Args: Clone + Send + Sync + 'static,
        State: Clone + Send + Sync + 'static,
    {
        self.clone().as_into_actionable().into_actionable(state)
    }
}

macro_rules! define_handler_for_tuple ({ $($param:ident)* } => {
    #[allow(non_snake_case, unused_mut, unused_variables)]
    impl<Func, Future, State, Output, $($param,)*>
        Handler<($($param,)*), State> for Func
    where
        Func: FnOnce($($param,)*) -> Future + Clone + Send + 'static,
        State: Send + Sync + 'static,
        Output: Into<Frame>,
        Future: std::future::Future<Output = Output> + Send + 'static,
        $(
            $param: Extractor<State> + Send,
        )*
    {
        type Future = crate::FrameFuture;

        fn invoke(&self, input: impl Into<Frame>, state: State) -> Self::Future {
            let handler = self.clone();
            let input = input.into();
            let action: ActionContext<State> = handler.context();
            let context = Context::with_action(action, state);

            crate::FrameFuture::from_async_block(async move {
                let input = &input;
                let handler = handler;
                let context = &context;

                $(
                    let $param = match $param::extract_from_frame_and_state(input.clone(), context) {
                        Ok(value) => value,
                        Err(rejection) => return Err(rejection.into()),
                    };
                )*

                let response = handler($($param,)*).await;

                Ok(response.into())
            })
        }
    }
});

define_handler_for_tuple! {}
define_handler_for_tuple! { A }
define_handler_for_tuple! { A B }
define_handler_for_tuple! { A B C }
define_handler_for_tuple! { A B C D }
define_handler_for_tuple! { A B C D E }
define_handler_for_tuple! { A B C D E F }
define_handler_for_tuple! { A B C D E F G }
define_handler_for_tuple! { A B C D E F G H }
define_handler_for_tuple! { A B C D E F G H I }
define_handler_for_tuple! { A B C D E F G H I J }
define_handler_for_tuple! { A B C D E F G H I J K }
define_handler_for_tuple! { A B C D E F G H I J K L }