qubit_state_machine/state_machine_builder.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026.
4 * Haixing Hu, Qubit Co. Ltd.
5 *
6 * All rights reserved.
7 *
8 ******************************************************************************/
9//! Builder for immutable state machine rules.
10
11use std::collections::{HashMap, HashSet};
12use std::fmt::Debug;
13use std::hash::Hash;
14
15use crate::{StateMachine, StateMachineBuildError, Transition};
16
17/// Builder used to define and validate finite state machine rules.
18///
19/// `S` is the state type and `E` is the event type. Configuration methods
20/// consume and return the builder so rule definitions can be chained. The built
21/// [`StateMachine`] is immutable.
22#[derive(Debug, Clone)]
23pub struct StateMachineBuilder<S, E>
24where
25 S: Copy + Eq + Hash + Debug,
26 E: Copy + Eq + Hash + Debug,
27{
28 pub(crate) states: HashSet<S>,
29 pub(crate) initial_states: HashSet<S>,
30 pub(crate) final_states: HashSet<S>,
31 pub(crate) transitions: Vec<Transition<S, E>>,
32}
33
34impl<S, E> StateMachineBuilder<S, E>
35where
36 S: Copy + Eq + Hash + Debug + 'static,
37 E: Copy + Eq + Hash + Debug + 'static,
38{
39 /// Creates an empty state machine builder.
40 ///
41 /// # Returns
42 /// A builder with no states or transitions.
43 pub fn new() -> Self {
44 Self {
45 states: HashSet::new(),
46 initial_states: HashSet::new(),
47 final_states: HashSet::new(),
48 transitions: Vec::new(),
49 }
50 }
51
52 /// Adds a state to the state machine definition.
53 ///
54 /// # Parameters
55 /// - `state`: State to register.
56 ///
57 /// # Returns
58 /// The updated builder.
59 pub fn add_state(mut self, state: S) -> Self {
60 self.states.insert(state);
61 self
62 }
63
64 /// Adds multiple states to the state machine definition.
65 ///
66 /// # Parameters
67 /// - `states`: States to register.
68 ///
69 /// # Returns
70 /// The updated builder.
71 pub fn add_states(mut self, states: &[S]) -> Self {
72 self.states.extend(states.iter().copied());
73 self
74 }
75
76 /// Marks a state as an initial state.
77 ///
78 /// The state must also be registered through [`add_state`](Self::add_state)
79 /// or [`add_states`](Self::add_states) before [`build`](Self::build) is
80 /// called.
81 ///
82 /// # Parameters
83 /// - `state`: Initial state to add.
84 ///
85 /// # Returns
86 /// The updated builder.
87 pub fn set_initial_state(mut self, state: S) -> Self {
88 self.initial_states.insert(state);
89 self
90 }
91
92 /// Marks multiple states as initial states.
93 ///
94 /// # Parameters
95 /// - `states`: Initial states to add.
96 ///
97 /// # Returns
98 /// The updated builder.
99 pub fn set_initial_states(mut self, states: &[S]) -> Self {
100 self.initial_states.extend(states.iter().copied());
101 self
102 }
103
104 /// Marks a state as a final state.
105 ///
106 /// The state must also be registered through [`add_state`](Self::add_state)
107 /// or [`add_states`](Self::add_states) before [`build`](Self::build) is
108 /// called.
109 ///
110 /// # Parameters
111 /// - `state`: Final state to add.
112 ///
113 /// # Returns
114 /// The updated builder.
115 pub fn set_final_state(mut self, state: S) -> Self {
116 self.final_states.insert(state);
117 self
118 }
119
120 /// Marks multiple states as final states.
121 ///
122 /// # Parameters
123 /// - `states`: Final states to add.
124 ///
125 /// # Returns
126 /// The updated builder.
127 pub fn set_final_states(mut self, states: &[S]) -> Self {
128 self.final_states.extend(states.iter().copied());
129 self
130 }
131
132 /// Adds a transition by source state, event, and target state.
133 ///
134 /// Source and target states must be registered before
135 /// [`build`](Self::build) is called. Adding the same transition more than
136 /// once is allowed. Adding the same `(source, event)` with a different
137 /// target is rejected during build.
138 ///
139 /// # Parameters
140 /// - `source`: State before the event is applied.
141 /// - `event`: Event that triggers the transition.
142 /// - `target`: State after the transition succeeds.
143 ///
144 /// # Returns
145 /// The updated builder.
146 pub fn add_transition(self, source: S, event: E, target: S) -> Self {
147 self.add_transition_value(Transition::new(source, event, target))
148 }
149
150 /// Adds a transition value.
151 ///
152 /// # Parameters
153 /// - `transition`: Transition to add to the state machine definition.
154 ///
155 /// # Returns
156 /// The updated builder.
157 pub fn add_transition_value(mut self, transition: Transition<S, E>) -> Self {
158 self.transitions.push(transition);
159 self
160 }
161
162 /// Builds an immutable state machine after validating the rule set.
163 ///
164 /// # Returns
165 /// A validated immutable state machine.
166 ///
167 /// # Errors
168 /// Returns a [`StateMachineBuildError`] when an initial state, final state,
169 /// transition source, or transition target is not registered, or when two
170 /// transitions map the same `(source, event)` pair to different targets.
171 pub fn build(self) -> Result<StateMachine<S, E>, StateMachineBuildError<S, E>> {
172 self.validate_registered_states()?;
173
174 let mut transition_set = HashSet::new();
175 let mut transition_map = HashMap::new();
176 for transition in &self.transitions {
177 let transition = *transition;
178 self.validate_transition(transition)?;
179 Self::insert_transition(transition, &mut transition_set, &mut transition_map)?;
180 }
181
182 Ok(StateMachine::new(self, transition_set, transition_map))
183 }
184
185 /// Validates that initial and final states are registered.
186 ///
187 /// # Returns
188 /// `Ok(())` when all configured state sets refer to registered states.
189 ///
190 /// # Errors
191 /// Returns the first unregistered initial or final state encountered.
192 fn validate_registered_states(&self) -> Result<(), StateMachineBuildError<S, E>> {
193 for state in &self.initial_states {
194 if !self.states.contains(state) {
195 return Err(StateMachineBuildError::InitialStateNotRegistered { state: *state });
196 }
197 }
198 for state in &self.final_states {
199 if !self.states.contains(state) {
200 return Err(StateMachineBuildError::FinalStateNotRegistered { state: *state });
201 }
202 }
203 Ok(())
204 }
205
206 /// Validates that a transition only references registered states.
207 ///
208 /// # Parameters
209 /// - `transition`: Transition to validate.
210 ///
211 /// # Returns
212 /// `Ok(())` when the transition source and target are registered.
213 ///
214 /// # Errors
215 /// Returns the missing source or target as a build error.
216 fn validate_transition(
217 &self,
218 transition: Transition<S, E>,
219 ) -> Result<(), StateMachineBuildError<S, E>> {
220 if !self.states.contains(&transition.source()) {
221 return Err(StateMachineBuildError::TransitionSourceNotRegistered {
222 source: transition.source(),
223 event: transition.event(),
224 target: transition.target(),
225 });
226 }
227 if !self.states.contains(&transition.target()) {
228 return Err(StateMachineBuildError::TransitionTargetNotRegistered {
229 source: transition.source(),
230 event: transition.event(),
231 target: transition.target(),
232 });
233 }
234 Ok(())
235 }
236
237 /// Inserts a transition into the set and lookup table.
238 ///
239 /// # Parameters
240 /// - `transition`: Transition to insert.
241 /// - `transition_set`: Set used for public transition inspection.
242 /// - `transition_map`: Lookup table used for event triggering.
243 ///
244 /// # Returns
245 /// `Ok(())` when the transition is inserted or is an exact duplicate.
246 ///
247 /// # Errors
248 /// Returns a duplicate-transition error if the same source and event already
249 /// point to a different target.
250 fn insert_transition(
251 transition: Transition<S, E>,
252 transition_set: &mut HashSet<Transition<S, E>>,
253 transition_map: &mut HashMap<(S, E), S>,
254 ) -> Result<(), StateMachineBuildError<S, E>> {
255 let source = transition.source();
256 let event = transition.event();
257 let target = transition.target();
258 if let Some(existing_target) = transition_map.get(&(source, event))
259 && *existing_target != target
260 {
261 return Err(StateMachineBuildError::DuplicateTransition {
262 source,
263 event,
264 existing_target: *existing_target,
265 new_target: target,
266 });
267 }
268 transition_set.insert(transition);
269 transition_map.insert((source, event), target);
270 Ok(())
271 }
272}
273
274impl<S, E> Default for StateMachineBuilder<S, E>
275where
276 S: Copy + Eq + Hash + Debug + 'static,
277 E: Copy + Eq + Hash + Debug + 'static,
278{
279 /// Creates an empty state machine builder.
280 fn default() -> Self {
281 Self::new()
282 }
283}