use tower::Service;
use crate::{Action, ActionContext, Actions, BoxedAction, Frame, FrameFuture, Handler};
use super::{Open, Stateful, Stateless};
#[derive(Clone)]
pub struct FirstMatch<Status, State> {
actions: Actions<State>,
status: Status,
}
impl<Status, State> FirstMatch<Status, State>
where
State: Clone + Send + Sync + 'static,
{
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
}
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();
FrameFuture::from_async_block(
async move { Ok(handle_first_match(actions, state, frame).await) },
)
}
}
impl<State> FirstMatch<Open, State> {
pub fn init() -> Self {
FirstMatch {
status: Open,
actions: Actions::new(),
}
}
pub fn with_state(&self, state: State) -> FirstMatch<Stateful<State>, State> {
FirstMatch {
actions: self.actions.clone(),
status: Stateful(state),
}
}
}
impl FirstMatch<Open, ()> {
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
self.handle_frame_with_state(frame, ())
}
pub fn without_state(self) -> FirstMatch<Stateless, ()> {
FirstMatch {
actions: self.actions,
status: Stateless,
}
}
}
impl FirstMatch<Stateless, ()> {
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
self.handle_frame_with_state(frame, ())
}
}
impl<State> FirstMatch<Stateful<State>, State>
where
State: Clone + Send + 'static,
{
pub fn state(&self) -> State {
self.status.0.clone()
}
pub fn handle_frame(self, frame: Frame) -> FrameFuture {
let actions = self.actions.clone();
let state = self.state().clone();
FrameFuture::from_async_block(
async move { Ok(handle_first_match(actions, state, frame).await) },
)
}
}
async fn handle_first_match<State>(
actions: Vec<BoxedAction<State>>,
state: State,
frame: Frame,
) -> Frame
where
State: Clone + Send + 'static,
{
for action in actions.into_iter() {
if let Ok(response) = action
.into_actionable(state.clone())
.call(frame.clone())
.await
{
return response;
}
}
Frame::default()
}
#[tokio::test]
async fn calling_with_a_frame_statelessly() -> Result<(), tower::BoxError> {
assert_eq!(
FirstMatch::init()
.without_state()
.handle_frame(Frame::default())
.await?,
Frame::default()
);
assert_eq!(
FirstMatch::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 = FirstMatch::init()
.with_state(ArbitraryState)
.handle_frame(Frame::default())
.await;
assert_eq!(result?, Frame::default());
Ok(())
}
#[tokio::test]
async fn getting_a_match() -> Result<(), tower::BoxError> {
use crate::{Context, Extractor, State};
async fn echo(frame: Frame, _: State<ArbitraryState>) -> Frame {
frame
}
async fn double_echo(frame: Frame, _: HelloWorld) -> Vec<Frame> {
vec![frame.clone(), frame]
}
#[derive(Clone)]
struct HelloWorld;
impl Extractor<ArbitraryState> for HelloWorld {
type Error = String;
fn extract(frame: Frame, _: &Context<ArbitraryState>) -> Result<Self, Self::Error> {
if frame.as_ref() == b"Hello, world!" {
Ok(Self)
} else {
Err("No match".to_string())
}
}
}
#[derive(Clone)]
struct ArbitraryState;
let system = FirstMatch::init().on_frame(double_echo).on_frame(echo);
assert_eq!(
system
.with_state(ArbitraryState)
.handle_frame(Frame::from(String::from("Hello, world!")))
.await?,
Frame::from(String::from("Hello, world!Hello, world!"))
);
assert_eq!(
system
.with_state(ArbitraryState)
.handle_frame(Frame::from(String::from("echo!")))
.await?,
Frame::from(String::from("echo!"))
);
Ok(())
}