nodo_runtime/
state_machine.rs1use core::fmt::{Debug, Formatter};
4use eyre::{Report, Result};
5use nodo::{
6 codelet::{Lifecycle, Transition},
7 core::DefaultStatus,
8};
9
10#[derive(Clone, Copy, PartialEq, Debug)]
12pub enum State {
13 Inactive,
15
16 Started,
18
19 Paused,
22
23 Error,
25}
26
27impl State {
28 pub fn transition(self, request: Transition) -> Option<State> {
30 match (self, request) {
31 (State::Started, Transition::Stop) | (State::Paused, Transition::Stop) => {
32 Some(State::Inactive)
33 }
34 (State::Inactive, Transition::Start)
35 | (State::Started, Transition::Step)
36 | (State::Paused, Transition::Resume) => Some(State::Started),
37 (State::Started, Transition::Pause) => Some(State::Paused),
38 (_, _) => None,
39 }
40 }
41}
42
43pub struct StateMachine<C> {
45 inner: C,
46 state: State,
47}
48
49#[derive(thiserror::Error, Debug)]
50pub enum TransitionError {
51 #[error("invalid transition {0:?} -> {1:?}")]
53 InvalidTransition(State, Transition),
54
55 #[error("execution failed [{0:?}]: {1:?}")]
57 ExecutionFailure(Transition, Report),
58}
59
60impl<C> StateMachine<C> {
61 pub fn new(inner: C) -> Self {
62 Self {
63 inner,
64 state: State::Inactive,
65 }
66 }
67
68 pub fn inner(&self) -> &C {
69 &self.inner
70 }
71
72 pub fn inner_mut(&mut self) -> &mut C {
73 &mut self.inner
74 }
75
76 pub fn state(&self) -> State {
77 self.state
78 }
79
80 pub fn is_valid_request(&self, request: Transition) -> bool {
81 self.state.transition(request).is_some()
82 }
83
84 pub fn transition(&mut self, transition: Transition) -> Result<DefaultStatus, TransitionError>
85 where
86 C: Lifecycle,
87 {
88 if let Some(next_state) = self.state.transition(transition) {
89 match self.inner.cycle(transition) {
90 Ok(kind) => {
91 self.state = next_state;
92 return Ok(kind);
93 }
94 Err(err) => {
95 self.state = State::Error;
96 return Err(TransitionError::ExecutionFailure(transition, err));
97 }
98 }
99 } else {
100 Err(TransitionError::InvalidTransition(self.state, transition))
101 }
102 }
103}
104
105impl<C> Debug for StateMachine<C> {
106 fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
107 fmt.debug_struct("StateMachine")
108 .field("inner", &"()")
109 .field("state", &self.state)
110 .finish()
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use crate::State;
117 use nodo::codelet::*;
118
119 #[test]
120 fn state_transition() {
121 assert_eq!(
122 State::Inactive.transition(Transition::Start),
123 Some(State::Started)
124 );
125 assert_eq!(
126 State::Started.transition(Transition::Step),
127 Some(State::Started)
128 );
129 assert_eq!(
130 State::Started.transition(Transition::Stop),
131 Some(State::Inactive)
132 );
133 }
134}