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}