mindset/builder/
mod.rs

1//! Builder API for ergonomic state machine construction.
2//!
3//! This module provides fluent builders and macros for creating state machines
4//! with minimal boilerplate while maintaining type safety.
5
6pub mod error;
7pub mod machine;
8pub mod macros;
9pub mod transition;
10
11pub use error::BuildError;
12pub use machine::StateMachineBuilder;
13pub use transition::TransitionBuilder;
14
15use crate::core::State;
16use crate::effects::{Transition, TransitionResult};
17use stillwater::prelude::*;
18
19/// Create a simple unconditional transition that succeeds.
20///
21/// # Example
22///
23/// ```
24/// use mindset::builder::simple_transition;
25/// use mindset::state_enum;
26///
27/// state_enum! {
28///     enum MyState {
29///         Start,
30///         End,
31///     }
32///     final: [End]
33/// }
34///
35/// let transition = simple_transition::<MyState, ()>(MyState::Start, MyState::End);
36/// ```
37pub fn simple_transition<S, Env>(from: S, to: S) -> Transition<S, Env>
38where
39    S: State + 'static,
40    Env: Clone + Send + Sync + 'static,
41{
42    let to_clone = to.clone();
43    TransitionBuilder::new()
44        .from(from)
45        .to(to)
46        .action(move || pure(TransitionResult::Success(to_clone.clone())).boxed())
47        .build()
48        .expect("Simple transition should always build")
49}
50
51/// Create a transition with a guard predicate.
52///
53/// # Example
54///
55/// ```
56/// use mindset::builder::guarded_transition;
57/// use mindset::state_enum;
58/// use mindset::core::State;
59///
60/// state_enum! {
61///     enum MyState {
62///         Start,
63///         Middle,
64///         End,
65///     }
66///     final: [End]
67/// }
68///
69/// let transition = guarded_transition::<MyState, (), _>(
70///     MyState::Start,
71///     MyState::Middle,
72///     |s| !s.is_final()
73/// );
74/// ```
75pub fn guarded_transition<S, Env, F>(from: S, to: S, guard: F) -> Transition<S, Env>
76where
77    S: State + 'static,
78    Env: Clone + Send + Sync + 'static,
79    F: Fn(&S) -> bool + Send + Sync + 'static,
80{
81    let to_clone = to.clone();
82    TransitionBuilder::new()
83        .from(from)
84        .to(to)
85        .when(guard)
86        .action(move || pure(TransitionResult::Success(to_clone.clone())).boxed())
87        .build()
88        .expect("Guarded transition should always build")
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use serde::{Deserialize, Serialize};
95
96    #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
97    enum TestState {
98        Start,
99        Middle,
100        End,
101    }
102
103    impl State for TestState {
104        fn name(&self) -> &str {
105            match self {
106                Self::Start => "Start",
107                Self::Middle => "Middle",
108                Self::End => "End",
109            }
110        }
111
112        fn is_final(&self) -> bool {
113            matches!(self, Self::End)
114        }
115    }
116
117    #[test]
118    fn simple_transition_builds() {
119        let transition = simple_transition::<TestState, ()>(TestState::Start, TestState::Middle);
120
121        assert_eq!(transition.from, TestState::Start);
122        assert_eq!(transition.to, TestState::Middle);
123        assert!(transition.can_execute(&TestState::Start));
124    }
125
126    #[test]
127    fn guarded_transition_respects_guard() {
128        let transition =
129            guarded_transition::<TestState, (), _>(TestState::Start, TestState::Middle, |s| {
130                !s.is_final()
131            });
132
133        assert!(transition.can_execute(&TestState::Start));
134        assert!(!transition.can_execute(&TestState::End));
135    }
136}