Skip to main content

state_machines_core/
lib.rs

1#![no_std]
2
3use core::fmt::Debug;
4
5#[cfg(feature = "inspect")]
6pub mod schema;
7
8#[cfg(feature = "inspect")]
9pub use schema::{EventSchema, Inspectable, MachineSchema, SuperstateSchema, TransitionSchema};
10
11/// Marker trait for states used by the generated state machines.
12pub trait MachineState: Copy + Eq + Debug + Send + Sync + 'static {}
13
14impl<T> MachineState for T where T: Copy + Eq + Debug + Send + Sync + 'static {}
15
16/// Marker trait indicating that a state is a substate of a superstate.
17///
18/// This enables polymorphic transitions from any substate to work as if
19/// they were from the superstate. For example:
20///
21/// ```rust,ignore
22/// // If LaunchPrep and Launching are substates of Flight:
23/// impl SubstateOf<Flight> for LaunchPrep {}
24/// impl SubstateOf<Flight> for Launching {}
25///
26/// // Then a transition "from Flight" can accept any Flight substate:
27/// impl<C, S: SubstateOf<Flight>> Machine<C, S> {
28///     pub fn abort(self) -> Machine<C, Standby> { ... }
29/// }
30/// ```
31pub trait SubstateOf<Super> {}
32
33/// Represents an error that occurred while attempting a transition.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct TransitionError<S>
36where
37    S: MachineState,
38{
39    pub from: S,
40    pub event: &'static str,
41    pub kind: TransitionErrorKind,
42}
43
44impl<S> TransitionError<S>
45where
46    S: MachineState,
47{
48    pub fn invalid_transition(from: S, event: &'static str) -> Self {
49        Self {
50            from,
51            event,
52            kind: TransitionErrorKind::InvalidTransition,
53        }
54    }
55
56    pub fn guard_failed(from: S, event: &'static str, guard: &'static str) -> Self {
57        Self {
58            from,
59            event,
60            kind: TransitionErrorKind::GuardFailed { guard },
61        }
62    }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum TransitionErrorKind {
67    InvalidTransition,
68    GuardFailed { guard: &'static str },
69    ActionFailed { action: &'static str },
70}
71
72pub type TransitionResult<S> = Result<(), TransitionError<S>>;
73
74/// Error returned when a guard or around callback fails in typestate mode.
75///
76/// In typestate machines, guards and around callbacks can fail even though the transition is valid.
77/// The machine is returned along with this error so the caller can retry or handle it.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct GuardError {
80    pub guard: &'static str,
81    pub event: &'static str,
82    pub kind: TransitionErrorKind,
83}
84
85impl GuardError {
86    pub const fn new(guard: &'static str, event: &'static str) -> Self {
87        Self {
88            guard,
89            event,
90            kind: TransitionErrorKind::GuardFailed { guard },
91        }
92    }
93
94    pub const fn with_kind(
95        guard: &'static str,
96        event: &'static str,
97        kind: TransitionErrorKind,
98    ) -> Self {
99        Self { guard, event, kind }
100    }
101}
102
103/// Error returned when a before/after callback returns a user-defined error.
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct CallbackError<E> {
106    pub action: &'static str,
107    pub event: &'static str,
108    pub source: E,
109}
110
111impl<E> CallbackError<E> {
112    pub fn new(action: &'static str, event: &'static str, source: E) -> Self {
113        Self {
114            action,
115            event,
116            source,
117        }
118    }
119}
120
121/// Error returned from typestate event methods.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum EventError<E> {
124    Guard(GuardError),
125    Callback(CallbackError<E>),
126}
127
128impl<E> EventError<E> {
129    pub const fn guard(err: GuardError) -> Self {
130        Self::Guard(err)
131    }
132
133    pub fn callback(action: &'static str, event: &'static str, source: E) -> Self {
134        Self::Callback(CallbackError::new(action, event, source))
135    }
136}
137
138#[doc(hidden)]
139pub trait FallibleCallbackReturn<E> {
140    fn into_result(self) -> Result<(), E>;
141}
142
143impl<E> FallibleCallbackReturn<E> for () {
144    fn into_result(self) -> Result<(), E> {
145        Ok(())
146    }
147}
148
149impl<E> FallibleCallbackReturn<E> for Result<(), E> {
150    fn into_result(self) -> Result<(), E> {
151        self
152    }
153}
154
155/// Error returned when dynamic dispatch fails.
156///
157/// This error type is used by the dynamic mode wrapper when runtime
158/// event dispatch encounters errors like invalid transitions or guard failures.
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub enum DynamicError<E = ()> {
161    /// Attempted to trigger an event that's not valid from the current state.
162    InvalidTransition {
163        from: &'static str,
164        event: &'static str,
165    },
166    /// A guard callback failed during the transition.
167    GuardFailed {
168        guard: &'static str,
169        event: &'static str,
170    },
171    /// An action callback failed during the transition.
172    ActionFailed {
173        action: &'static str,
174        event: &'static str,
175    },
176    /// A before/after callback returned a user-defined error.
177    CallbackFailed {
178        action: &'static str,
179        event: &'static str,
180        source: E,
181    },
182    /// Attempted to access or modify state data when in wrong state.
183    WrongState {
184        expected: &'static str,
185        actual: &'static str,
186        operation: &'static str,
187    },
188}
189
190impl<E> DynamicError<E> {
191    pub fn invalid_transition(from: &'static str, event: &'static str) -> Self {
192        Self::InvalidTransition { from, event }
193    }
194
195    pub fn guard_failed(guard: &'static str, event: &'static str) -> Self {
196        Self::GuardFailed { guard, event }
197    }
198
199    pub fn action_failed(action: &'static str, event: &'static str) -> Self {
200        Self::ActionFailed { action, event }
201    }
202
203    pub fn callback_failed(action: &'static str, event: &'static str, source: E) -> Self {
204        Self::CallbackFailed {
205            action,
206            event,
207            source,
208        }
209    }
210
211    pub fn wrong_state(
212        expected: &'static str,
213        actual: &'static str,
214        operation: &'static str,
215    ) -> Self {
216        Self::WrongState {
217            expected,
218            actual,
219            operation,
220        }
221    }
222
223    /// Convert from GuardError to DynamicError.
224    pub fn from_guard_error(err: GuardError) -> Self {
225        match err.kind {
226            TransitionErrorKind::GuardFailed { guard } => Self::GuardFailed {
227                guard,
228                event: err.event,
229            },
230            TransitionErrorKind::ActionFailed { action } => Self::ActionFailed {
231                action,
232                event: err.event,
233            },
234            TransitionErrorKind::InvalidTransition => Self::InvalidTransition {
235                from: "",
236                event: err.event,
237            },
238        }
239    }
240
241    pub fn from_event_error(err: EventError<E>) -> Self {
242        match err {
243            EventError::Guard(err) => Self::from_guard_error(err),
244            EventError::Callback(err) => Self::callback_failed(err.action, err.event, err.source),
245        }
246    }
247}
248
249pub trait Machine {
250    type State: MachineState;
251
252    fn state(&self) -> Self::State;
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub struct TransitionContext<S>
257where
258    S: MachineState,
259{
260    pub from: S,
261    pub to: S,
262    pub event: &'static str,
263}
264
265impl<S> TransitionContext<S>
266where
267    S: MachineState,
268{
269    pub const fn new(from: S, to: S, event: &'static str) -> Self {
270        Self { from, to, event }
271    }
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub enum AroundStage {
276    Before,
277    AfterSuccess,
278}
279
280#[derive(Debug, Clone)]
281pub enum AroundOutcome<S>
282where
283    S: MachineState,
284{
285    Proceed,
286    Abort(TransitionError<S>),
287}
288
289#[derive(Debug, Clone)]
290pub struct TransitionDefinition<S>
291where
292    S: MachineState,
293{
294    pub sources: &'static [S],
295    pub target: S,
296    pub guards: &'static [&'static str],
297    pub unless: &'static [&'static str],
298    pub before: &'static [&'static str],
299    pub after: &'static [&'static str],
300    pub around: &'static [&'static str],
301}
302
303#[derive(Debug, Clone)]
304pub struct EventDefinition<S>
305where
306    S: MachineState,
307{
308    pub name: &'static str,
309    pub guards: &'static [&'static str],
310    pub before: &'static [&'static str],
311    pub after: &'static [&'static str],
312    pub around: &'static [&'static str],
313    pub payload: Option<&'static str>,
314    pub transitions: &'static [TransitionDefinition<S>],
315}
316
317#[derive(Debug, Clone)]
318pub struct SuperstateDefinition<S>
319where
320    S: MachineState,
321{
322    pub name: &'static str,
323    pub descendants: &'static [S],
324    pub initial: S,
325}
326
327#[derive(Debug, Clone)]
328pub struct MachineDefinition<S>
329where
330    S: MachineState,
331{
332    pub name: &'static str,
333    pub states: &'static [S],
334    pub initial: S,
335    pub async_mode: bool,
336    pub superstates: &'static [SuperstateDefinition<S>],
337    pub events: &'static [EventDefinition<S>],
338}