statemachine_rs/machine/
builder.rs

1use std::{cell::RefCell, marker::PhantomData};
2
3use super::{error::StateMachineError, BasicStateMachine, StateWrapper};
4
5pub trait StateMachineBuilder<State, Input, Transition>
6where
7    Transition: Fn(&State, Input) -> State,
8    State: Clone,
9{
10    type Output;
11
12    /// Starts the builder.
13    fn start() -> Self;
14
15    /// Sets particular initial state to the state machine.
16    fn initial_state(self, state: State) -> Self;
17
18    /// Sets particular state to the current state.
19    fn current_state(self, state: State) -> Self;
20
21    /// Sets particular transition algorithm to the state machine.
22    fn transition(self, next: Transition) -> Self;
23
24    /// To finish the builder. If it fails, returns [`crate::machine::error::StateMachineError`].
25    fn build(self) -> Result<Self::Output, Box<dyn std::error::Error>>;
26}
27
28/// This builder enables us to assemble StateMachine
29/// (like [`crate::machine::BasicStateMachine`]) more easily.
30pub struct BasicStateMachineBuilder<State, Input, Transition>
31where
32    Transition: Fn(&State, Input) -> State,
33    State: Clone,
34{
35    initial_state: Option<State>,
36    current_state: Option<State>,
37    transition: Option<Transition>,
38    _marker: PhantomData<Input>,
39}
40
41impl<State, Input, Transition> StateMachineBuilder<State, Input, Transition>
42    for BasicStateMachineBuilder<State, Input, Transition>
43where
44    Transition: Fn(&State, Input) -> State,
45    State: Clone,
46{
47    type Output = BasicStateMachine<State, Input, Transition>;
48
49    fn start() -> Self {
50        Self::default()
51    }
52
53    fn initial_state(mut self, state: State) -> Self {
54        self.initial_state = Some(state);
55        self
56    }
57
58    fn current_state(mut self, state: State) -> Self {
59        self.current_state = Some(state);
60        self
61    }
62
63    fn transition(mut self, next: Transition) -> Self {
64        self.transition = Some(next);
65        self
66    }
67
68    fn build(self) -> Result<Self::Output, Box<dyn std::error::Error>> {
69        match (self.initial_state, self.transition) {
70            (Some(initial_state), Some(transition)) => Ok(BasicStateMachine {
71                initial_state: initial_state.clone(),
72                current_state: {
73                    // If `current_state` in this builder is still `None`,
74                    // sets `initial_state` as the current state forcibly.
75                    let current_state = self.current_state;
76                    match current_state {
77                        Some(state) => RefCell::new(StateWrapper::new(state)),
78                        None => RefCell::new(StateWrapper::new(initial_state)),
79                    }
80                },
81                transition,
82                _maker: self._marker,
83            }),
84            (None, _) => Err(Box::new(StateMachineError::MissingField(
85                "initial_state".to_string(),
86            ))),
87            (_, None) => Err(Box::new(StateMachineError::MissingField(
88                "transition".to_string(),
89            ))),
90        }
91    }
92}
93
94impl<State, Input, Transition> Default for BasicStateMachineBuilder<State, Input, Transition>
95where
96    Transition: Fn(&State, Input) -> State,
97    State: Clone,
98{
99    fn default() -> Self {
100        BasicStateMachineBuilder {
101            initial_state: None,
102            current_state: None,
103            transition: None,
104            _marker: PhantomData::<Input>::default(),
105        }
106    }
107}
108
109#[cfg(test)]
110mod test {
111    use super::{BasicStateMachineBuilder, StateMachineBuilder};
112    use crate::machine::StateMachine;
113
114    #[allow(dead_code)]
115    #[derive(Copy, Clone, Debug, PartialEq)]
116    enum Stations {
117        Shibuya,
118        IkejiriOhashi,
119        Sangendyaya,
120        KomazawaDaigaku,
121        Sakurashinmachi,
122        Yoga,
123        FutakoTamagawa,
124    }
125
126    #[allow(dead_code)]
127    enum Train {
128        Local,
129        Express,
130    }
131
132    #[test]
133    fn test_build() {
134        // sets only initial state
135        let sm = BasicStateMachineBuilder::start()
136            .initial_state(Stations::Shibuya)
137            .transition(|station, train| match (station, train) {
138                (Stations::Shibuya, Train::Local) => Stations::IkejiriOhashi,
139                (Stations::Shibuya, Train::Express) => Stations::Sangendyaya,
140                (Stations::IkejiriOhashi, Train::Local) => Stations::Sangendyaya,
141                (Stations::Sangendyaya, Train::Local) => Stations::KomazawaDaigaku,
142                (Stations::Sangendyaya, Train::Express) => Stations::FutakoTamagawa,
143                (Stations::KomazawaDaigaku, Train::Local) => Stations::Sakurashinmachi,
144                (Stations::Sakurashinmachi, Train::Local) => Stations::Yoga,
145                _ => unreachable!(),
146            })
147            .build()
148            .unwrap();
149
150        assert_eq!(Stations::Shibuya, sm.current_state());
151
152        // sets current state after initializing initial state
153        let sm = BasicStateMachineBuilder::start()
154            .initial_state(Stations::Shibuya)
155            .current_state(Stations::Sangendyaya)
156            .transition(|station, train| match (station, train) {
157                (Stations::Shibuya, Train::Local) => Stations::IkejiriOhashi,
158                (Stations::Shibuya, Train::Express) => Stations::Sangendyaya,
159                (Stations::IkejiriOhashi, Train::Local) => Stations::Sangendyaya,
160                (Stations::Sangendyaya, Train::Local) => Stations::KomazawaDaigaku,
161                (Stations::Sangendyaya, Train::Express) => Stations::FutakoTamagawa,
162                (Stations::KomazawaDaigaku, Train::Local) => Stations::Sakurashinmachi,
163                (Stations::Sakurashinmachi, Train::Local) => Stations::Yoga,
164                _ => unreachable!(),
165            })
166            .build()
167            .unwrap();
168
169        assert_eq!(Stations::Sangendyaya, sm.current_state());
170    }
171
172    #[test]
173    fn test_fail_initial_state() {
174        let sm = BasicStateMachineBuilder::start()
175            .transition(|station, train| match (station, train) {
176                (Stations::Shibuya, Train::Local) => Stations::IkejiriOhashi,
177                (Stations::Shibuya, Train::Express) => Stations::Sangendyaya,
178                (Stations::IkejiriOhashi, Train::Local) => Stations::Sangendyaya,
179                (Stations::Sangendyaya, Train::Local) => Stations::KomazawaDaigaku,
180                (Stations::Sangendyaya, Train::Express) => Stations::FutakoTamagawa,
181                (Stations::KomazawaDaigaku, Train::Local) => Stations::Sakurashinmachi,
182                (Stations::Sakurashinmachi, Train::Local) => Stations::Yoga,
183                _ => unreachable!(),
184            })
185            .build();
186
187        assert!(sm.is_err());
188    }
189}