stateforward-sml 1.2.0

sml.rs: a no-std Rust implementation of the sml.cpp transition-table DSL
Documentation
use sml::sml;

pub struct Run;
pub struct Finish;

sml! {
    ExceptionDsl {
        *"idle"_s + event<Run> / fail_action,
         "idle"_s + exception<_> / recover = "recovered"_s,
         "recovered"_s + event<Finish> = X,
    }
}

#[derive(Default)]
struct Context {
    recovered: bool,
}

impl ExceptionDslStateMachineContext for Context {
    fn fail_action(&mut self, _event: &Run) -> Result<(), ()> {
        Err(())
    }

    fn recover(&mut self) -> Result<(), ()> {
        self.recovered = true;
        Ok(())
    }
}

#[test]
fn wildcard_exception_transition_handles_action_result_error() {
    let mut sm = ExceptionDslStateMachine::new(Context::default());
    sm.process_event(Run).unwrap();

    assert!(sm.is(&ExceptionDslStates::Recovered));
    assert!(sm.context().recovered);
    sm.process_event(Finish).unwrap();
    assert!(sm.is_terminated());
}

pub struct AsyncRun;

sml! {
    AsyncExceptionDsl {
        *"idle"_s + event<AsyncRun> [async fail_guard] = X,
         "idle"_s + exception<_> / async async_recover = "recovered"_s,
    }
}

#[derive(Default)]
struct AsyncContext {
    recovered: bool,
}

impl AsyncExceptionDslStateMachineContext for AsyncContext {
    async fn fail_guard(&self, _event: &AsyncRun) -> Result<bool, ()> {
        Err(())
    }

    async fn async_recover(&mut self) -> Result<(), ()> {
        self.recovered = true;
        Ok(())
    }
}

#[test]
fn async_exception_transition_handles_guard_result_error() {
    smol::block_on(async {
        let mut sm = AsyncExceptionDslStateMachine::new(AsyncContext::default());
        sm.process_event(AsyncRun).await.unwrap();
        assert!(sm.is(&AsyncExceptionDslStates::Recovered));
        assert!(sm.context().recovered);
    });
}

#[derive(Debug, PartialEq)]
pub struct Failure {
    code: u32,
}

pub struct TypedRun;

sml! {
    TypedExceptionDsl {
        *"idle"_s + event<TypedRun> / typed_fail,
         "idle"_s + exception<Failure> / capture_failure = "recovered"_s,
    }
}

#[derive(Default)]
struct TypedContext {
    code: Option<u32>,
}

impl TypedExceptionDslStateMachineContext for TypedContext {
    fn typed_fail(&mut self, _event: &TypedRun) -> Result<(), Failure> {
        Err(Failure { code: 42 })
    }

    fn capture_failure(&mut self, error: &Failure) -> Result<(), Failure> {
        self.code = Some(error.code);
        Ok(())
    }
}

#[test]
fn typed_exception_infers_and_injects_callback_error() {
    let mut sm = TypedExceptionDslStateMachine::new(TypedContext::default());
    sm.process_event(TypedRun).unwrap();

    assert!(sm.is(&TypedExceptionDslStates::Recovered));
    assert_eq!(sm.context().code, Some(42));
}