Skip to main content

statecraft/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[macro_use]
4mod macros;
5
6/// Derive for [`State`] trait
7/// ```
8/// # use statecraft::*;
9/// #[derive(State)]
10/// struct MyState;
11/// ```
12pub use statecraft_derive::State;
13
14/// Derive for [`Stateful`] trait
15///
16/// Users of this derive must specify the [`State`] with an attribute. For example:
17/// ```
18/// # use statecraft::*;
19/// #[derive(Stateful)]
20/// #[state(S)]
21/// struct MyMachine<S: State> {
22///     state: S,
23/// }
24/// ```
25pub use statecraft_derive::Stateful;
26
27/// Derive for [`SelfTransitionable`]
28///
29/// Users must also implement [`Stateful`], and can do so via the provided derive
30/// ```
31/// # use statecraft::*;
32/// #[derive(Stateful, SelfTransitionable)]
33/// #[state(S)]
34/// struct MyMachine<S: State> {
35///     state: S,
36/// }
37/// ```
38pub use statecraft_derive::SelfTransitionable;
39
40/// A state which a [`Stateful`] can be in
41///
42/// The [`State`] trait itself is bare, only used for type enforcement of generic [`Stateful`] objects.
43pub trait State {}
44
45/// Types which can be in one of any given number of [`State`]s at a given time
46pub trait Stateful {
47    /// The `State` type a given `Stateful` can be in
48    type State: State;
49}
50
51/// [`Stateful`]s which can transition between different [`State`]s
52///
53/// A transition between [`State`]s for [`Stateful`]s implementing [`Transitionable`] **must** be
54/// successful. If the transition between different [`State`]s can fail, see the
55/// [`TryTransitionable`] trait.
56pub trait Transitionable<N>: Stateful
57where
58    N: State,
59{
60    /// The [`Stateful`] that the transition will force the current [`Stateful`] into
61    type NextStateful: Stateful<State = N>;
62
63    /// Transition [`Self`] into the [`Self::NextStateful`]
64    ///
65    /// Note that [`Self`] is consumed by this method. This enforces that a [`Stateful`] cannot
66    /// exist in multiple [`State`]s at the same time.
67    fn transition(self) -> Self::NextStateful;
68}
69
70mod async_transitionable {
71    use super::*;
72
73    /// Async variant of [`Transitionable`]
74    #[trait_variant::make(AsyncTransitionable: Send)]
75    #[allow(dead_code)]
76    pub trait LocalAsyncTransitionable<N>: Stateful
77    where
78        N: State,
79    {
80        /// The [`Stateful`] that the transition will force the current [`Stateful`] into
81        type NextStateful: Stateful<State = N>;
82
83        /// Transition [`Self`] into the [`Self::NextStateful`]
84        ///
85        /// Note that [`Self`] is consumed by this method. This enforces that a [`Stateful`] cannot
86        /// exist in multiple [`State`]s at the same time.
87        async fn transition(self) -> Self::NextStateful;
88    }
89}
90pub use async_transitionable::AsyncTransitionable;
91
92impl<S, N> AsyncTransitionable<N> for S
93where
94    S: Stateful + Transitionable<N> + Send,
95    N: State,
96{
97    type NextStateful = S::NextStateful;
98
99    async fn transition(self) -> Self::NextStateful {
100        <Self as Transitionable<N>>::transition(self)
101    }
102}
103
104/// [`Stateful`]s which can transition back to themselves
105///
106/// This is a helper trait enabling simpler implementations of [`TryTransitionable`] for
107/// [`Stateful`]s where the [`TryTransitionable::FailureStateful`] is the current [`Stateful`]. A
108/// blanket implementation for [`Transitionable`] exists so that all [`SelfTransitionable`]
109/// [`Stateful`]s can transition back to themselves.
110pub trait SelfTransitionable: Stateful {}
111
112impl<S: SelfTransitionable> Transitionable<S::State> for S {
113    type NextStateful = Self;
114    fn transition(self) -> Self::NextStateful {
115        self
116    }
117}
118
119/// Message-only error produced by the [`return_recover!`](crate::return_recover) macro family
120///
121/// The macros convert this into the implementor's error type via `Into`, so implementors with a
122/// [`TryTransitionable::Error`] other than `TransitionError` must provide a
123/// `From<TransitionError>` impl to use them:
124///
125/// ```
126/// use statecraft::{return_recover, Recovered, SelfTransitionable, State, Stateful, TransitionError, Transitionable, TryTransitionable};
127///
128/// # #[derive(State)]
129/// # struct Init;
130/// # #[derive(State)]
131/// # struct Running;
132/// # #[derive(Stateful, SelfTransitionable)]
133/// # #[state(S)]
134/// # struct Server<S: State> {
135/// #     state: S,
136/// # }
137/// #[derive(Debug)]
138/// enum ServerError {
139///     Transition(TransitionError),
140/// }
141///
142/// impl From<TransitionError> for ServerError {
143///     fn from(err: TransitionError) -> Self {
144///         ServerError::Transition(err)
145///     }
146/// }
147///
148/// impl TryTransitionable<Running, Init> for Server<Init> {
149///     type SuccessStateful = Server<Running>;
150///     type FailureStateful = Server<Init>;
151///     type Error = ServerError;
152///
153///     fn try_transition(
154///         self,
155///     ) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>> {
156///         return_recover!(self, "We always fail :(")
157///     }
158/// }
159/// ```
160///
161/// A `TransitionError` [`TryTransitionable::Error`] converts as-is, and a boxed error type like
162/// `Box<dyn std::error::Error + Send + Sync>` converts through the standard library's blanket
163/// boxing impl.
164#[derive(Debug)]
165pub struct TransitionError {
166    message: String,
167}
168
169impl TransitionError {
170    pub fn new(message: impl Into<String>) -> Self {
171        TransitionError {
172            message: message.into(),
173        }
174    }
175}
176
177impl std::fmt::Display for TransitionError {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        f.write_str(&self.message)
180    }
181}
182
183impl std::error::Error for TransitionError {}
184
185/// The error value of a failed [`TryTransitionable`]
186///
187/// If a [`TryTransitionable::try_transition`] fails, a [`Recovered`] struct is returned. This
188/// struct contains the recovered stateful of the failed transition, and an error value which
189/// caused the [`TryTransitionable::try_transition`] to fail.
190#[derive(Debug)]
191pub struct Recovered<S, E> {
192    /// The new [`Stateful`] after a failed [`TryTransitionable::try_transition`]
193    pub stateful: S,
194
195    /// The error which caused [`TryTransitionable::try_transition`] to fail
196    pub error: E,
197}
198
199impl<S, E> Recovered<S, E> {
200    /// Convenience function to create a new [`Recovered`] struct
201    pub fn new(stateful: S, error: E) -> Self {
202        Recovered { stateful, error }
203    }
204}
205
206/// [`Stateful`]s which can transition between different [`State`]s, but **may fail** when
207/// attempting to do so
208///
209/// A transition between [`State`]s for a [`Stateful`] implementing [`TryTransitionable`] **may
210/// fail**. If the [`Self::try_transition`] succeeds, a new [`TryTransitionable::SuccessStateful`]
211/// is returned. However if the [`Self::try_transition`] fails, a [`Recovered`] is returned. Type
212/// bounds on the [`Self::FailureStateful`] type ensures that the current [`Stateful`] can
213/// [`Transitionable::transition`] to [`Self::FailureStateful`] without error.
214///
215/// Many implementors will return the current [`Stateful`] if they fail. [`Stateful`]s with this
216/// behavior should implement the [`SelfTransitionable`] trait.
217pub trait TryTransitionable<N, R>: Transitionable<R>
218where
219    N: State,
220    R: State,
221{
222    /// New [`Stateful`] if the [`Self::try_transition`] succeeds
223    type SuccessStateful: Stateful<State = N>;
224
225    /// New [`Stateful`] if the [`Self::try_transition`] fails. This will be encapsulated in a
226    /// [`Recovered`]
227    type FailureStateful: Stateful<State = R>;
228
229    /// The error which caused the [`Self::try_transition`] to fail
230    type Error;
231
232    /// Attempt to transition the current [`Stateful`] into [`Self::SuccessStateful`]
233    ///
234    /// On success, a new [`Self::SuccessStateful`] is returned. On failure, a [`Recovered`] is
235    /// returned, with the new [`Self::FailureStateful`] and the error which caused the failure
236    fn try_transition(
237        self,
238    ) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>>;
239}
240
241mod async_try_transitionable {
242    use super::*;
243
244    /// Async variant of [`TryTransitionable`]
245    #[trait_variant::make(AsyncTryTransitionable: Send)]
246    #[allow(dead_code)]
247    pub trait LocalAsyncTryTransitionable<N, R>: AsyncTransitionable<R>
248    where
249        N: State,
250        R: State,
251    {
252        /// New [`Stateful`] if the [`Self::try_transition`] succeeds
253        type SuccessStateful: Stateful<State = N>;
254
255        /// New [`Stateful`] if the [`Self::try_transition`] fails. This will be encapsulated in a
256        /// [`Recovered`]
257        type FailureStateful: Stateful<State = R>;
258
259        /// The error which caused the [`Self::try_transition`] to fail
260        type Error;
261
262        /// Attempt to transition the current [`Stateful`] into [`Self::SuccessStateful`]
263        ///
264        /// On success, a new [`Self::SuccessStateful`] is returned. On failure, a [`Recovered`] is
265        /// returned, with the new [`Self::FailureStateful`] and the error which caused the failure
266        async fn try_transition(
267            self,
268        ) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>>;
269    }
270}
271pub use async_try_transitionable::AsyncTryTransitionable;
272
273/// Convenience trait to convert [`Result<S, Recovered<F, E>>`] into the error `E`
274///
275/// Useful for when users of a [`TryTransitionable`] [`Stateful`] do not want to recover the state
276/// machine in the [`Recovered`] state.
277///
278/// Note this is implemented via a blanket implementation on all [`Result<S, Recovered<F, E>>`]
279/// types. It should probably not be implemented manually.
280pub trait Bailable<N, R>
281where
282    N: State,
283    R: State,
284{
285    /// The [`Stateful`] a [`TryTransitionable`] returns on success
286    type SuccessStateful: Stateful<State = N>;
287    /// The error a [`TryTransitionable`] returns via [`Recovered`] on error
288    type Error;
289
290    /// Get the underlying error from a failed [`TryTransitionable::try_transition`], or the new
291    /// [`Stateful`] if the transition was successful.
292    fn into_error(self) -> Result<Self::SuccessStateful, Self::Error>;
293}
294
295impl<N, R, S, F, E> Bailable<N, R> for Result<S, Recovered<F, E>>
296where
297    N: State,
298    R: State,
299    S: Stateful<State = N>,
300    F: Stateful<State = R>,
301{
302    type SuccessStateful = S;
303    type Error = E;
304
305    fn into_error(self) -> Result<Self::SuccessStateful, Self::Error> {
306        self.map_err(|recovered| recovered.error)
307    }
308}
309
310#[cfg(test)]
311mod test {
312    use crate::{
313        AsyncTryTransitionable, Recovered, SelfTransitionable, State, Stateful, TransitionError,
314        Transitionable, TryTransitionable,
315    };
316
317    use test_log::test;
318
319    macro_rules! empty_states {
320        ($($state:ident),+) => {
321            $(
322                #[derive(State, Debug, Eq, PartialEq)]
323                struct $state;
324            )*
325        };
326    }
327
328    macro_rules! transitions {
329        ($machine:ident, $(($start:ident,$end:ident)),+) => {
330            #[derive(Stateful, Debug, Eq, PartialEq)]
331            #[state(S)]
332            struct $machine<S: State> {
333                state: S
334            }
335            $(
336                impl Transitionable<$end> for $machine<$start> {
337                    type NextStateful = $machine<$end>;
338
339                    fn transition(self) -> Self::NextStateful {
340                        $machine { state: $end }
341                    }
342                }
343            )*
344        };
345    }
346
347    #[test]
348    fn stateful_derive_with_preceding_attrs() {
349        empty_states!(A);
350
351        /// Doc comment preceding the state attribute
352        #[derive(Stateful, Debug)]
353        #[state(A)]
354        struct Machine {
355            state: A,
356        }
357
358        let machine = Machine { state: A };
359        assert_eq!(machine.state, A);
360    }
361
362    #[test]
363    fn two_state() {
364        empty_states!(A, B);
365        transitions!(Machine, (A, B), (B, A));
366
367        let a = Machine { state: A };
368        let b: Machine<B> = a.transition();
369        assert_eq!(b.state, B);
370    }
371
372    #[test]
373    fn several_state() {
374        empty_states!(A, B, C, D, E);
375        transitions!(
376            Machine,
377            (A, B),
378            (B, C),
379            (C, D),
380            (D, E),
381            (E, A),
382            (D, C),
383            (D, B)
384        );
385
386        let a = Machine { state: A };
387        let b: Machine<B> = a.transition();
388        let c: Machine<C> = b.transition();
389        let d: Machine<D> = c.transition();
390        let b: Machine<B> = <_ as Transitionable<B>>::transition(d);
391        assert_eq!(b.state, B);
392        let c: Machine<C> = b.transition();
393        let d: Machine<D> = c.transition();
394        let c: Machine<C> = <_ as Transitionable<C>>::transition(d);
395        assert_eq!(c.state, C);
396        let d: Machine<D> = c.transition();
397        assert_eq!(d.state, D);
398        let e: Machine<E> = <_ as Transitionable<E>>::transition(d);
399        assert_eq!(e.state, E);
400        let a: Machine<A> = e.transition();
401        assert_eq!(a.state, A);
402    }
403
404    #[test(tokio::test)]
405    async fn fallible_transition() {
406        empty_states!(A, B, C);
407        transitions!(Machine, (A, B), (B, A), (C, A));
408
409        impl Machine<B> {
410            fn should_transition(&mut self) -> bool {
411                use std::sync::atomic::{AtomicBool, Ordering};
412
413                static SHOULD_TRANSITION: AtomicBool = AtomicBool::new(false);
414                SHOULD_TRANSITION.swap(true, Ordering::Relaxed)
415            }
416        }
417
418        impl TryTransitionable<C, A> for Machine<B> {
419            type SuccessStateful = Machine<C>;
420            type FailureStateful = Machine<A>;
421            type Error = Box<dyn std::error::Error + Send + Sync>;
422
423            fn try_transition(
424                mut self,
425            ) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>>
426            {
427                if self.should_transition() {
428                    Ok(Machine { state: C })
429                } else {
430                    Err(Recovered {
431                        stateful: Machine { state: A },
432                        error: "Failed".into(),
433                    })
434                }
435            }
436        }
437
438        let a = Machine { state: A };
439        let b: Machine<B> = a.transition();
440
441        let recovered = b.try_transition().err().unwrap();
442        assert_eq!(recovered.stateful.state, A);
443
444        let success = recovered.stateful.transition().try_transition().unwrap();
445        assert_eq!(success.state, C);
446    }
447
448    #[test(tokio::test)]
449    async fn async_macro_recovery() {
450        empty_states!(A, B);
451
452        #[derive(Stateful, SelfTransitionable, Debug, Eq, PartialEq)]
453        #[state(S)]
454        struct Machine<S: State> {
455            state: S,
456        }
457
458        impl AsyncTryTransitionable<B, A> for Machine<A> {
459            type SuccessStateful = Machine<B>;
460            type FailureStateful = Machine<A>;
461            type Error = TransitionError;
462
463            async fn try_transition(
464                self,
465            ) -> Result<Self::SuccessStateful, Recovered<Self::FailureStateful, Self::Error>>
466            {
467                return_recover_async!(self, "always fails");
468            }
469        }
470
471        let a = Machine { state: A };
472        let recovered = try_transition_async!(a, B).err().unwrap();
473        assert_eq!(recovered.stateful.state, A);
474    }
475}