use std::fmt::Debug;
use crate::{Action, ActionContext, Actions, BoxedAction, Frame, FrameFuture, Handler};
use super::{dispatchable::Dispatchable, Open, Stateful, Stateless};
#[derive(Clone)]
pub struct DispatchBatch<Status, State> {
actions: Actions<State>,
status: Status,
}
impl<State: Debug> Debug for DispatchBatch<Open, State> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Dispatch")
.field("actions", &self.actions.len())
.field("status", &self.status)
.finish()
}
}
impl<Status, State> DispatchBatch<Status, State>
where
State: Clone + Send + Sync + 'static,
{
pub fn on<ActionHandler, Args>(
mut self,
pattern: impl AsRef<str>,
action: ActionHandler,
) -> Self
where
ActionHandler: Handler<Args, State> + Clone + Send + Sync + 'static,
Args: Clone + Send + Sync + 'static,
{
let action = Dispatchable::new(pattern.as_ref(), Action::new(action));
self.actions.push(Box::new(action));
self
}
pub fn action_context(&self) -> ActionContext<State> {
ActionContext::Unit
}
pub fn handle_frame_with_state(&self, frame: Frame, state: State) -> FrameFuture {
let actions = self.actions.clone();
let state = state.clone();
FrameFuture::from_async_block(async move { handle_dispatch(actions, state, frame).await })
}
}
async fn handle_dispatch<State>(
actions: Vec<BoxedAction<State>>,
state: State,
frame: Frame,
) -> crate::Result<Frame>
where
State: Clone + Send + 'static,
{
let mut responses = vec![];
for action in actions.into_iter() {
responses.push(
action
.into_actionable(state.clone())
.handle_frame(frame.clone())
.await?,
);
}
Ok(responses.into())
}
impl<State> DispatchBatch<Open, State> {
pub fn init() -> Self {
Self {
actions: Vec::new(),
status: Open,
}
}
pub fn with_state(&self, state: State) -> DispatchBatch<Stateful<State>, State> {
DispatchBatch {
actions: self.actions.clone(),
status: Stateful(state),
}
}
}
impl DispatchBatch<Open, ()> {
pub fn without_state(self) -> DispatchBatch<Stateless, ()> {
DispatchBatch {
actions: self.actions,
status: Stateless,
}
}
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
self.handle_frame_with_state(frame, ())
}
}
impl DispatchBatch<Stateless, ()> {
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
self.handle_frame_with_state(frame, ())
}
}
impl<State> DispatchBatch<Stateful<State>, State>
where
State: Clone + Send + Sync + 'static,
{
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
let state = self.state();
self.handle_frame_with_state(frame, state)
}
pub fn state(&self) -> State {
self.status.0.clone()
}
}
#[tokio::test]
async fn calling_with_a_frame_statelessly() -> Result<(), tower::BoxError> {
assert_eq!(
DispatchBatch::init()
.without_state()
.handle_frame(Frame::default())
.await?,
Frame::default()
);
assert_eq!(
DispatchBatch::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;
assert_eq!(
DispatchBatch::init()
.with_state(ArbitraryState)
.handle_frame(Frame::default())
.await?,
Frame::default()
);
Ok(())
}
#[tokio::test]
async fn calls_full_matches_statelessly() -> Result<(), tower::BoxError> {
let string_id = |given_string: String| async { given_string };
let system = DispatchBatch::init().on("match", string_id);
let message = Frame::message("match", "test".to_owned(), ());
let result = system.without_state().handle_frame(message).await;
assert_eq!(Frame::from(result), "test".to_string().into());
Ok(())
}
#[tokio::test]
async fn calls_full_matches_with_state() -> Result<(), tower::BoxError> {
#[derive(Clone)]
struct ArbitraryState;
let string_id = |given_string: String, _: crate::State<ArbitraryState>| async { given_string };
let system = DispatchBatch::init().on("match", string_id);
let message = Frame::message("match", "test".to_owned(), ());
let result = system
.with_state(ArbitraryState)
.handle_frame(message)
.await;
assert_eq!(Frame::from(result), "test".to_string().into());
Ok(())
}
#[tokio::test]
async fn ignores_stateless_misses() -> Result<(), tower::BoxError> {
let string_id = |given_string: String| async { given_string };
let system = DispatchBatch::init().on("match", string_id);
let message = Frame::message("miss", "test".to_owned(), ());
let result = system.without_state().handle_frame(message).await;
assert_eq!(Frame::from(result), ().into());
Ok(())
}
#[tokio::test]
async fn ignores_stateful_misses() -> Result<(), tower::BoxError> {
#[derive(Clone)]
struct ArbitraryState;
let string_id = |given_string: String, _: crate::State<ArbitraryState>| async { given_string };
let system = DispatchBatch::init().on("match", string_id);
let message = Frame::message("miss", "test".to_owned(), ());
let result = system
.with_state(ArbitraryState)
.handle_frame(message)
.await;
assert_eq!(Frame::from(result), ().into());
Ok(())
}