mindset/builder/
macros.rs

1//! Macros for ergonomic state machine construction.
2
3/// Generate State trait implementation for simple enums.
4///
5/// # Example
6///
7/// ```
8/// use mindset::state_enum;
9///
10/// state_enum! {
11///     pub enum WorkflowState {
12///         Start,
13///         Processing,
14///         Done,
15///         Failed,
16///     }
17///     final: [Done, Failed]
18///     error: [Failed]
19/// }
20/// ```
21#[macro_export]
22macro_rules! state_enum {
23    (
24        $(#[$meta:meta])*
25        $vis:vis enum $name:ident {
26            $(
27                $(#[$variant_meta:meta])*
28                $variant:ident
29            ),* $(,)?
30        }
31
32        $(final: [$($final:ident),* $(,)?])?
33        $(error: [$($error:ident),* $(,)?])?
34    ) => {
35        $(#[$meta])*
36        #[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
37        $vis enum $name {
38            $(
39                $(#[$variant_meta])*
40                $variant
41            ),*
42        }
43
44        impl $crate::core::State for $name {
45            fn name(&self) -> &str {
46                match self {
47                    $(Self::$variant => stringify!($variant)),*
48                }
49            }
50
51            fn is_final(&self) -> bool {
52                match self {
53                    $($(Self::$final => true,)*)?
54                    _ => false,
55                }
56            }
57
58            fn is_error(&self) -> bool {
59                match self {
60                    $($(Self::$error => true,)*)?
61                    _ => false,
62                }
63            }
64        }
65    };
66}
67
68#[cfg(test)]
69mod tests {
70    use crate::core::State;
71
72    state_enum! {
73        enum TestState {
74            Initial,
75            Processing,
76            Complete,
77            Failed,
78        }
79        final: [Complete, Failed]
80        error: [Failed]
81    }
82
83    #[test]
84    fn state_enum_macro_generates_trait() {
85        let state = TestState::Initial;
86        assert_eq!(state.name(), "Initial");
87        assert!(!state.is_final());
88        assert!(!state.is_error());
89
90        let complete = TestState::Complete;
91        assert!(complete.is_final());
92        assert!(!complete.is_error());
93
94        let failed = TestState::Failed;
95        assert!(failed.is_final());
96        assert!(failed.is_error());
97    }
98
99    #[test]
100    fn state_enum_supports_visibility() {
101        // The macro should work with pub visibility
102        state_enum! {
103            pub enum PublicState {
104                A,
105                B,
106            }
107            final: [B]
108        }
109
110        let _state = PublicState::A;
111    }
112
113    #[test]
114    fn state_enum_works_without_final_error() {
115        state_enum! {
116            enum MinimalState {
117                One,
118                Two,
119            }
120        }
121
122        let state = MinimalState::One;
123        assert!(!state.is_final());
124        assert!(!state.is_error());
125    }
126}