#![doc = include_str!("../README.md")]
#[macro_use]
mod macros;
pub use statecraft_derive::State;
pub use statecraft_derive::Stateful;
pub use statecraft_derive::SelfTransitionable;
pub trait State {}
pub trait Stateful {
type State: State;
}
pub trait Transitionable<N>: Stateful
where
N: State,
{
type NextStateful: Stateful<State = N>;
fn transition(self) -> Self::NextStateful;
}
mod async_transitionable {
use super::*;
#[trait_variant::make(AsyncTransitionable: Send)]
#[allow(dead_code)]
pub trait LocalAsyncTransitionable<N>: Stateful
where
N: State,
{
type NextStateful: Stateful<State = N>;
async fn transition(self) -> Self::NextStateful;
}
}
pub use async_transitionable::AsyncTransitionable;
impl<S, N> AsyncTransitionable<N> for S
where
S: Stateful + Transitionable<N> + Send,
N: State,
{
type NextStateful = S::NextStateful;
async fn transition(self) -> Self::NextStateful {
<Self as Transitionable<N>>::transition(self)
}
}
pub trait SelfTransitionable: Stateful {}
impl<S: SelfTransitionable> Transitionable<S::State> for S {
type NextStateful = Self;
fn transition(self) -> Self::NextStateful {
self
}
}
#[derive(Debug)]
pub struct TransitionError {
message: String,
}
impl TransitionError {
pub fn new(message: impl Into<String>) -> Self {
TransitionError {
message: message.into(),
}
}
}
impl std::fmt::Display for TransitionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for TransitionError {}
#[derive(Debug)]
pub struct Recovered<S, E> {
pub stateful: S,
pub error: E,
}
impl<S, E> Recovered<S, E> {
pub fn new(stateful: S, error: E) -> Self {
Recovered { stateful, error }
}
}
pub trait TryTransitionable<N, R>: Transitionable<R>
where
N: State,
R: State,
{
type SuccessStateful: Stateful<State = N>;
type FailureStateful: Stateful<State = R>;
type Error;
fn try_transition(
self,
) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>>;
}
mod async_try_transitionable {
use super::*;
#[trait_variant::make(AsyncTryTransitionable: Send)]
#[allow(dead_code)]
pub trait LocalAsyncTryTransitionable<N, R>: AsyncTransitionable<R>
where
N: State,
R: State,
{
type SuccessStateful: Stateful<State = N>;
type FailureStateful: Stateful<State = R>;
type Error;
async fn try_transition(
self,
) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>>;
}
}
pub use async_try_transitionable::AsyncTryTransitionable;
pub trait Bailable<N, R>
where
N: State,
R: State,
{
type SuccessStateful: Stateful<State = N>;
type Error;
fn into_error(self) -> Result<Self::SuccessStateful, Self::Error>;
}
impl<N, R, S, F, E> Bailable<N, R> for Result<S, Recovered<F, E>>
where
N: State,
R: State,
S: Stateful<State = N>,
F: Stateful<State = R>,
{
type SuccessStateful = S;
type Error = E;
fn into_error(self) -> Result<Self::SuccessStateful, Self::Error> {
self.map_err(|recovered| recovered.error)
}
}
#[cfg(test)]
mod test {
use crate::{
AsyncTryTransitionable, Recovered, SelfTransitionable, State, Stateful, TransitionError,
Transitionable, TryTransitionable,
};
use test_log::test;
macro_rules! empty_states {
($($state:ident),+) => {
$(
#[derive(State, Debug, Eq, PartialEq)]
struct $state;
)*
};
}
macro_rules! transitions {
($machine:ident, $(($start:ident,$end:ident)),+) => {
#[derive(Stateful, Debug, Eq, PartialEq)]
#[state(S)]
struct $machine<S: State> {
state: S
}
$(
impl Transitionable<$end> for $machine<$start> {
type NextStateful = $machine<$end>;
fn transition(self) -> Self::NextStateful {
$machine { state: $end }
}
}
)*
};
}
#[test]
fn stateful_derive_with_preceding_attrs() {
empty_states!(A);
#[derive(Stateful, Debug)]
#[state(A)]
struct Machine {
state: A,
}
let machine = Machine { state: A };
assert_eq!(machine.state, A);
}
#[test]
fn two_state() {
empty_states!(A, B);
transitions!(Machine, (A, B), (B, A));
let a = Machine { state: A };
let b: Machine<B> = a.transition();
assert_eq!(b.state, B);
}
#[test]
fn several_state() {
empty_states!(A, B, C, D, E);
transitions!(
Machine,
(A, B),
(B, C),
(C, D),
(D, E),
(E, A),
(D, C),
(D, B)
);
let a = Machine { state: A };
let b: Machine<B> = a.transition();
let c: Machine<C> = b.transition();
let d: Machine<D> = c.transition();
let b: Machine<B> = <_ as Transitionable<B>>::transition(d);
assert_eq!(b.state, B);
let c: Machine<C> = b.transition();
let d: Machine<D> = c.transition();
let c: Machine<C> = <_ as Transitionable<C>>::transition(d);
assert_eq!(c.state, C);
let d: Machine<D> = c.transition();
assert_eq!(d.state, D);
let e: Machine<E> = <_ as Transitionable<E>>::transition(d);
assert_eq!(e.state, E);
let a: Machine<A> = e.transition();
assert_eq!(a.state, A);
}
#[test(tokio::test)]
async fn fallible_transition() {
empty_states!(A, B, C);
transitions!(Machine, (A, B), (B, A), (C, A));
impl Machine<B> {
fn should_transition(&mut self) -> bool {
use std::sync::atomic::{AtomicBool, Ordering};
static SHOULD_TRANSITION: AtomicBool = AtomicBool::new(false);
SHOULD_TRANSITION.swap(true, Ordering::Relaxed)
}
}
impl TryTransitionable<C, A> for Machine<B> {
type SuccessStateful = Machine<C>;
type FailureStateful = Machine<A>;
type Error = Box<dyn std::error::Error + Send + Sync>;
fn try_transition(
mut self,
) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>>
{
if self.should_transition() {
Ok(Machine { state: C })
} else {
Err(Recovered {
stateful: Machine { state: A },
error: "Failed".into(),
})
}
}
}
let a = Machine { state: A };
let b: Machine<B> = a.transition();
let recovered = b.try_transition().err().unwrap();
assert_eq!(recovered.stateful.state, A);
let success = recovered.stateful.transition().try_transition().unwrap();
assert_eq!(success.state, C);
}
#[test(tokio::test)]
async fn async_macro_recovery() {
empty_states!(A, B);
#[derive(Stateful, SelfTransitionable, Debug, Eq, PartialEq)]
#[state(S)]
struct Machine<S: State> {
state: S,
}
impl AsyncTryTransitionable<B, A> for Machine<A> {
type SuccessStateful = Machine<B>;
type FailureStateful = Machine<A>;
type Error = TransitionError;
async fn try_transition(
self,
) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>>
{
return_recover_async!(self, "always fails");
}
}
let a = Machine { state: A };
let recovered = try_transition_async!(a, B).err().unwrap();
assert_eq!(recovered.stateful.state, A);
}
}