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 fn start() -> Self;
14
15 fn initial_state(self, state: State) -> Self;
17
18 fn current_state(self, state: State) -> Self;
20
21 fn transition(self, next: Transition) -> Self;
23
24 fn build(self) -> Result<Self::Output, Box<dyn std::error::Error>>;
26}
27
28pub 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 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 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 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}