use core::fmt::{Debug, Formatter};
use eyre::{Report, Result};
use nodo::{
codelet::{Lifecycle, Transition},
core::DefaultStatus,
};
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum State {
Inactive,
Started,
Paused,
Error,
}
impl State {
pub fn transition(self, request: Transition) -> Option<State> {
match (self, request) {
(State::Started, Transition::Stop) | (State::Paused, Transition::Stop) => {
Some(State::Inactive)
}
(State::Inactive, Transition::Start)
| (State::Started, Transition::Step)
| (State::Paused, Transition::Resume) => Some(State::Started),
(State::Started, Transition::Pause) => Some(State::Paused),
(_, _) => None,
}
}
}
pub struct StateMachine<C> {
inner: C,
state: State,
}
#[derive(thiserror::Error, Debug)]
pub enum TransitionError {
#[error("invalid transition {0:?} -> {1:?}")]
InvalidTransition(State, Transition),
#[error("execution failed [{0:?}]: {1:?}")]
ExecutionFailure(Transition, Report),
}
impl<C> StateMachine<C> {
pub fn new(inner: C) -> Self {
Self {
inner,
state: State::Inactive,
}
}
pub fn inner(&self) -> &C {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut C {
&mut self.inner
}
pub fn state(&self) -> State {
self.state
}
pub fn is_valid_request(&self, request: Transition) -> bool {
self.state.transition(request).is_some()
}
pub fn transition(&mut self, transition: Transition) -> Result<DefaultStatus, TransitionError>
where
C: Lifecycle,
{
if let Some(next_state) = self.state.transition(transition) {
match self.inner.cycle(transition) {
Ok(kind) => {
self.state = next_state;
return Ok(kind);
}
Err(err) => {
self.state = State::Error;
return Err(TransitionError::ExecutionFailure(transition, err));
}
}
} else {
Err(TransitionError::InvalidTransition(self.state, transition))
}
}
}
impl<C> Debug for StateMachine<C> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
fmt.debug_struct("StateMachine")
.field("inner", &"()")
.field("state", &self.state)
.finish()
}
}
#[cfg(test)]
mod tests {
use crate::State;
use nodo::codelet::*;
#[test]
fn state_transition() {
assert_eq!(
State::Inactive.transition(Transition::Start),
Some(State::Started)
);
assert_eq!(
State::Started.transition(Transition::Step),
Some(State::Started)
);
assert_eq!(
State::Started.transition(Transition::Stop),
Some(State::Inactive)
);
}
}