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 dynamic dispatch fails.
104///
105/// This error type is used by the dynamic mode wrapper when runtime
106/// event dispatch encounters errors like invalid transitions or guard failures.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub enum DynamicError {
109    /// Attempted to trigger an event that's not valid from the current state.
110    InvalidTransition {
111        from: &'static str,
112        event: &'static str,
113    },
114    /// A guard callback failed during the transition.
115    GuardFailed {
116        guard: &'static str,
117        event: &'static str,
118    },
119    /// An action callback failed during the transition.
120    ActionFailed {
121        action: &'static str,
122        event: &'static str,
123    },
124    /// Attempted to access or modify state data when in wrong state.
125    WrongState {
126        expected: &'static str,
127        actual: &'static str,
128        operation: &'static str,
129    },
130}
131
132impl DynamicError {
133    pub fn invalid_transition(from: &'static str, event: &'static str) -> Self {
134        Self::InvalidTransition { from, event }
135    }
136
137    pub fn guard_failed(guard: &'static str, event: &'static str) -> Self {
138        Self::GuardFailed { guard, event }
139    }
140
141    pub fn action_failed(action: &'static str, event: &'static str) -> Self {
142        Self::ActionFailed { action, event }
143    }
144
145    pub fn wrong_state(
146        expected: &'static str,
147        actual: &'static str,
148        operation: &'static str,
149    ) -> Self {
150        Self::WrongState {
151            expected,
152            actual,
153            operation,
154        }
155    }
156
157    /// Convert from GuardError to DynamicError.
158    pub fn from_guard_error(err: GuardError) -> Self {
159        match err.kind {
160            TransitionErrorKind::GuardFailed { guard } => Self::GuardFailed {
161                guard,
162                event: err.event,
163            },
164            TransitionErrorKind::ActionFailed { action } => Self::ActionFailed {
165                action,
166                event: err.event,
167            },
168            TransitionErrorKind::InvalidTransition => Self::InvalidTransition {
169                from: "",
170                event: err.event,
171            },
172        }
173    }
174}
175
176pub trait Machine {
177    type State: MachineState;
178
179    fn state(&self) -> Self::State;
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub struct TransitionContext<S>
184where
185    S: MachineState,
186{
187    pub from: S,
188    pub to: S,
189    pub event: &'static str,
190}
191
192impl<S> TransitionContext<S>
193where
194    S: MachineState,
195{
196    pub const fn new(from: S, to: S, event: &'static str) -> Self {
197        Self { from, to, event }
198    }
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub enum AroundStage {
203    Before,
204    AfterSuccess,
205}
206
207#[derive(Debug, Clone)]
208pub enum AroundOutcome<S>
209where
210    S: MachineState,
211{
212    Proceed,
213    Abort(TransitionError<S>),
214}
215
216#[derive(Debug, Clone)]
217pub struct TransitionDefinition<S>
218where
219    S: MachineState,
220{
221    pub sources: &'static [S],
222    pub target: S,
223    pub guards: &'static [&'static str],
224    pub unless: &'static [&'static str],
225    pub before: &'static [&'static str],
226    pub after: &'static [&'static str],
227    pub around: &'static [&'static str],
228}
229
230#[derive(Debug, Clone)]
231pub struct EventDefinition<S>
232where
233    S: MachineState,
234{
235    pub name: &'static str,
236    pub guards: &'static [&'static str],
237    pub before: &'static [&'static str],
238    pub after: &'static [&'static str],
239    pub around: &'static [&'static str],
240    pub payload: Option<&'static str>,
241    pub transitions: &'static [TransitionDefinition<S>],
242}
243
244#[derive(Debug, Clone)]
245pub struct SuperstateDefinition<S>
246where
247    S: MachineState,
248{
249    pub name: &'static str,
250    pub descendants: &'static [S],
251    pub initial: S,
252}
253
254#[derive(Debug, Clone)]
255pub struct MachineDefinition<S>
256where
257    S: MachineState,
258{
259    pub name: &'static str,
260    pub states: &'static [S],
261    pub initial: S,
262    pub async_mode: bool,
263    pub superstates: &'static [SuperstateDefinition<S>],
264    pub events: &'static [EventDefinition<S>],
265}