intrepid-core 0.2.0

Manage complex async business logic with ease
Documentation
use tower::Service;

use crate::{Action, ActionContext, Actions, Frame, FrameFuture, Handler};

use super::{Open, Stateful, Stateless};

/// A system that is still being initialized.
#[derive(Clone)]
pub struct Direct<Status, State> {
    actions: Actions<State>,
    status: Status,
}

impl<Status, State> Direct<Status, State>
where
    State: Clone + Send + Sync + 'static,
{
    /// Add an action to the system.
    pub fn on_frame<ActionHandler, Args>(mut self, action: ActionHandler) -> Self
    where
        ActionHandler: Handler<Args, State> + Clone + Send + Sync + 'static,
        Args: Clone + Send + Sync + 'static,
    {
        self.actions.push(Box::new(Action::new(action)));
        self
    }

    /// Get the (empty) action context for this system.
    pub fn action_context(&self) -> ActionContext<State> {
        ActionContext::Unit
    }

    /// Handle a frame and state with this system.
    pub fn handle_frame_with_state(&self, frame: Frame, state: State) -> FrameFuture {
        let mut responses = vec![];

        for action in self.actions.clone().into_iter() {
            responses.push(
                action
                    .into_actionable(state.clone())
                    .handle_frame(frame.clone()),
            );
        }

        FrameFuture::from_frame_futures(responses)
    }
}

impl<State> Direct<Open, State> {
    /// Create a new direct system with a given state. Direct systems do not route message
    /// frames to matching patterns, but instead invoke all actions.
    pub fn init() -> Self {
        Direct {
            status: Open,
            actions: Actions::new(),
        }
    }

    /// Transition to a stateful system with a given state.
    pub fn with_state(&self, state: State) -> Direct<Stateful<State>, State> {
        Direct {
            actions: self.actions.clone(),
            status: Stateful(state),
        }
    }
}

impl Direct<Open, ()> {
    /// Handle a frame and state with this system.
    pub fn handle_frame(self, frame: Frame) -> FrameFuture {
        self.handle_frame_with_state(frame, ())
    }

    /// Create a new direct system without state.
    pub fn without_state(self) -> Direct<Stateless, ()> {
        Direct {
            actions: self.actions,
            status: Stateless,
        }
    }
}

impl Direct<Stateless, ()> {
    /// Handle a frame and state with this system.
    pub fn handle_frame(self, frame: Frame) -> FrameFuture {
        self.handle_frame_with_state(frame, ())
    }
}

impl<State> Direct<Stateful<State>, State>
where
    State: Clone + Send + 'static,
{
    /// Get the state for this system.
    pub fn state(&self) -> State {
        self.status.0.clone()
    }

    /// Handle a frame with this system.
    pub fn handle_frame(self, frame: Frame) -> FrameFuture {
        let mut responses = vec![];

        for action in self.actions.clone().into_iter() {
            responses.push(action.into_actionable(self.state()).call(frame.clone()));
        }

        FrameFuture::from_frame_futures(responses)
    }
}

#[tokio::test]
async fn calling_with_a_frame_statelessly() -> Result<(), tower::BoxError> {
    assert_eq!(
        Direct::init()
            .without_state()
            .handle_frame(Frame::default())
            .await?,
        Frame::default()
    );

    assert_eq!(
        Direct::init().handle_frame(Frame::default()).await?,
        Frame::default()
    );

    Ok(())
}

#[tokio::test]
async fn calling_with_a_frame_statefully() -> Result<(), tower::BoxError> {
    #[derive(Clone)]
    struct ArbitraryState;
    let result = Direct::init()
        .with_state(ArbitraryState)
        .handle_frame(Frame::default())
        .await;

    assert_eq!(result?, Frame::default());

    Ok(())
}