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
72/// Error returned when a guard or around callback fails in typestate mode.
73///
74/// In typestate machines, guards and around callbacks can fail even though the transition is valid.
75/// The machine is returned along with this error so the caller can retry or handle it.
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct GuardError {
78    pub guard: &'static str,
79    pub event: &'static str,
80    pub kind: TransitionErrorKind,
81}
82
83impl GuardError {
84    pub const fn new(guard: &'static str, event: &'static str) -> Self {
85        Self {
86            guard,
87            event,
88            kind: TransitionErrorKind::GuardFailed { guard },
89        }
90    }
91
92    pub const fn with_kind(
93        guard: &'static str,
94        event: &'static str,
95        kind: TransitionErrorKind,
96    ) -> Self {
97        Self { guard, event, kind }
98    }
99}
100
101/// Error returned when a before/after callback returns a user-defined error.
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct CallbackError<E> {
104    pub action: &'static str,
105    pub event: &'static str,
106    pub source: E,
107}
108
109impl<E> CallbackError<E> {
110    pub fn new(action: &'static str, event: &'static str, source: E) -> Self {
111        Self {
112            action,
113            event,
114            source,
115        }
116    }
117}
118
119/// Error returned from typestate event methods.
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub enum EventError<E> {
122    Guard(GuardError),
123    Callback(CallbackError<E>),
124}
125
126impl<E> EventError<E> {
127    pub const fn guard(err: GuardError) -> Self {
128        Self::Guard(err)
129    }
130
131    pub fn callback(action: &'static str, event: &'static str, source: E) -> Self {
132        Self::Callback(CallbackError::new(action, event, source))
133    }
134}
135
136#[doc(hidden)]
137pub trait FallibleCallbackReturn<E> {
138    fn into_result(self) -> Result<(), E>;
139}
140
141impl<E> FallibleCallbackReturn<E> for () {
142    fn into_result(self) -> Result<(), E> {
143        Ok(())
144    }
145}
146
147impl<E> FallibleCallbackReturn<E> for Result<(), E> {
148    fn into_result(self) -> Result<(), E> {
149        self
150    }
151}
152
153/// Error returned when dynamic dispatch fails.
154///
155/// This error type is used by the dynamic mode wrapper when runtime
156/// event dispatch encounters errors like invalid transitions or guard failures.
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum DynamicError<E = ()> {
159    /// Attempted to trigger an event that's not valid from the current state.
160    InvalidTransition {
161        from: &'static str,
162        event: &'static str,
163    },
164    /// A guard callback failed during the transition.
165    GuardFailed {
166        guard: &'static str,
167        event: &'static str,
168    },
169    /// An action callback failed during the transition.
170    ActionFailed {
171        action: &'static str,
172        event: &'static str,
173    },
174    /// A before/after callback returned a user-defined error.
175    CallbackFailed {
176        action: &'static str,
177        event: &'static str,
178        source: E,
179    },
180    /// Attempted to access or modify state data when in wrong state.
181    WrongState {
182        expected: &'static str,
183        actual: &'static str,
184        operation: &'static str,
185    },
186}
187
188impl<E> DynamicError<E> {
189    pub fn invalid_transition(from: &'static str, event: &'static str) -> Self {
190        Self::InvalidTransition { from, event }
191    }
192
193    pub fn guard_failed(guard: &'static str, event: &'static str) -> Self {
194        Self::GuardFailed { guard, event }
195    }
196
197    pub fn action_failed(action: &'static str, event: &'static str) -> Self {
198        Self::ActionFailed { action, event }
199    }
200
201    pub fn callback_failed(action: &'static str, event: &'static str, source: E) -> Self {
202        Self::CallbackFailed {
203            action,
204            event,
205            source,
206        }
207    }
208
209    pub fn wrong_state(
210        expected: &'static str,
211        actual: &'static str,
212        operation: &'static str,
213    ) -> Self {
214        Self::WrongState {
215            expected,
216            actual,
217            operation,
218        }
219    }
220
221    /// Convert from GuardError to DynamicError.
222    pub fn from_guard_error(err: GuardError) -> Self {
223        match err.kind {
224            TransitionErrorKind::GuardFailed { guard } => Self::GuardFailed {
225                guard,
226                event: err.event,
227            },
228            TransitionErrorKind::ActionFailed { action } => Self::ActionFailed {
229                action,
230                event: err.event,
231            },
232            TransitionErrorKind::InvalidTransition => Self::InvalidTransition {
233                from: "",
234                event: err.event,
235            },
236        }
237    }
238
239    pub fn from_event_error(err: EventError<E>) -> Self {
240        match err {
241            EventError::Guard(err) => Self::from_guard_error(err),
242            EventError::Callback(err) => Self::callback_failed(err.action, err.event, err.source),
243        }
244    }
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum AroundStage {
249    Before,
250    AfterSuccess,
251}
252
253#[derive(Debug, Clone)]
254pub enum AroundOutcome<S>
255where
256    S: MachineState,
257{
258    Proceed,
259    Abort(TransitionError<S>),
260}