/ crates / bot / src / state.rs
state.rs
  1  /// Bot state management with typestate pattern
  2  ///
  3  /// Provides type-safe state transitions using phantom types to encode
  4  /// bot lifecycle states at compile time.
  5  
  6  use crate::Result;
  7  use serde::{Deserialize, Serialize};
  8  use std::marker::PhantomData;
  9  
 10  /// Bot state enumeration
 11  #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
 12  pub enum BotState {
 13      /// Initial state
 14      Created,
 15      /// Setup phase
 16      Initializing,
 17      /// Running and executing behaviors
 18      Running,
 19      /// Paused (can be resumed)
 20      Paused,
 21      /// Shutting down
 22      Stopping,
 23      /// Fully stopped
 24      Stopped,
 25      /// Error state
 26      Error,
 27  }
 28  
 29  impl std::fmt::Display for BotState {
 30      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 31          match self {
 32              BotState::Created => write!(f, "created"),
 33              BotState::Initializing => write!(f, "initializing"),
 34              BotState::Running => write!(f, "running"),
 35              BotState::Paused => write!(f, "paused"),
 36              BotState::Stopping => write!(f, "stopping"),
 37              BotState::Stopped => write!(f, "stopped"),
 38              BotState::Error => write!(f, "error"),
 39          }
 40      }
 41  }
 42  
 43  /// State transition result
 44  #[derive(Debug, Clone, Serialize, Deserialize)]
 45  pub struct StateTransition {
 46      /// Previous state
 47      pub from: BotState,
 48      /// New state
 49      pub to: BotState,
 50      /// Timestamp of transition
 51      pub timestamp_ms: i64,
 52      /// Optional message
 53      pub message: Option<String>,
 54  }
 55  
 56  impl StateTransition {
 57      pub fn new(from: BotState, to: BotState) -> Self {
 58          Self {
 59              from,
 60              to,
 61              timestamp_ms: std::time::SystemTime::now()
 62                  .duration_since(std::time::UNIX_EPOCH)
 63                  .unwrap_or_default()
 64                  .as_millis() as i64,
 65              message: None,
 66          }
 67      }
 68  
 69      pub fn with_message(mut self, message: String) -> Self {
 70          self.message = Some(message);
 71          self
 72      }
 73  }
 74  
 75  /// Type-safe state machine using phantom types
 76  /// This allows compile-time enforcement of valid state transitions
 77  pub struct StateMachine<S> {
 78      current: BotState,
 79      history: Vec<StateTransition>,
 80      _marker: PhantomData<S>,
 81  }
 82  
 83  /// Phantom types for different states
 84  pub struct Created;
 85  pub struct Initializing;
 86  pub struct Running;
 87  pub struct Paused;
 88  pub struct Stopping;
 89  pub struct Stopped;
 90  pub struct Error;
 91  
 92  impl StateMachine<Created> {
 93      pub fn new() -> Self {
 94          Self {
 95              current: BotState::Created,
 96              history: Vec::new(),
 97              _marker: PhantomData,
 98          }
 99      }
100  
101      pub fn initialize(mut self) -> StateMachine<Initializing> {
102          let transition = StateTransition::new(self.current, BotState::Initializing);
103          self.history.push(transition);
104          StateMachine {
105              current: BotState::Initializing,
106              history: self.history,
107              _marker: PhantomData,
108          }
109      }
110  }
111  
112  impl StateMachine<Initializing> {
113      pub fn start(mut self) -> StateMachine<Running> {
114          let transition = StateTransition::new(self.current, BotState::Running);
115          self.history.push(transition);
116          StateMachine {
117              current: BotState::Running,
118              history: self.history,
119              _marker: PhantomData,
120          }
121      }
122  
123      pub fn fail(mut self, message: String) -> StateMachine<Error> {
124          let transition = StateTransition::new(self.current, BotState::Error)
125              .with_message(message);
126          self.history.push(transition);
127          StateMachine {
128              current: BotState::Error,
129              history: self.history,
130              _marker: PhantomData,
131          }
132      }
133  }
134  
135  impl StateMachine<Running> {
136      pub fn pause(mut self) -> StateMachine<Paused> {
137          let transition = StateTransition::new(self.current, BotState::Paused);
138          self.history.push(transition);
139          StateMachine {
140              current: BotState::Paused,
141              history: self.history,
142              _marker: PhantomData,
143          }
144      }
145  
146      pub fn stop(mut self) -> StateMachine<Stopping> {
147          let transition = StateTransition::new(self.current, BotState::Stopping);
148          self.history.push(transition);
149          StateMachine {
150              current: BotState::Stopping,
151              history: self.history,
152              _marker: PhantomData,
153          }
154      }
155  
156      pub fn fail(mut self, message: String) -> StateMachine<Error> {
157          let transition = StateTransition::new(self.current, BotState::Error)
158              .with_message(message);
159          self.history.push(transition);
160          StateMachine {
161              current: BotState::Error,
162              history: self.history,
163              _marker: PhantomData,
164          }
165      }
166  }
167  
168  impl StateMachine<Paused> {
169      pub fn resume(mut self) -> StateMachine<Running> {
170          let transition = StateTransition::new(self.current, BotState::Running);
171          self.history.push(transition);
172          StateMachine {
173              current: BotState::Running,
174              history: self.history,
175              _marker: PhantomData,
176          }
177      }
178  
179      pub fn stop(mut self) -> StateMachine<Stopping> {
180          let transition = StateTransition::new(self.current, BotState::Stopping);
181          self.history.push(transition);
182          StateMachine {
183              current: BotState::Stopping,
184              history: self.history,
185              _marker: PhantomData,
186          }
187      }
188  }
189  
190  impl StateMachine<Stopping> {
191      pub fn complete(mut self) -> StateMachine<Stopped> {
192          let transition = StateTransition::new(self.current, BotState::Stopped);
193          self.history.push(transition);
194          StateMachine {
195              current: BotState::Stopped,
196              history: self.history,
197              _marker: PhantomData,
198          }
199      }
200  }
201  
202  impl<S> StateMachine<S> {
203      pub fn current_state(&self) -> BotState {
204          self.current
205      }
206  
207      pub fn history(&self) -> &[StateTransition] {
208          &self.history
209      }
210  }
211  
212  impl Default for StateMachine<Created> {
213      fn default() -> Self {
214          Self::new()
215      }
216  }
217  
218  #[cfg(test)]
219  mod tests {
220      use super::*;
221  
222      #[test]
223      fn test_state_machine_happy_path() {
224          // Created -> Initializing -> Running -> Stopping -> Stopped
225          let sm = StateMachine::new();
226          assert_eq!(sm.current_state(), BotState::Created);
227  
228          let sm = sm.initialize();
229          assert_eq!(sm.current_state(), BotState::Initializing);
230  
231          let sm = sm.start();
232          assert_eq!(sm.current_state(), BotState::Running);
233  
234          let sm = sm.stop();
235          assert_eq!(sm.current_state(), BotState::Stopping);
236  
237          let sm = sm.complete();
238          assert_eq!(sm.current_state(), BotState::Stopped);
239  
240          // Check history
241          assert_eq!(sm.history().len(), 4);
242      }
243  
244      #[test]
245      fn test_state_machine_with_pause() {
246          let sm = StateMachine::new();
247          let sm = sm.initialize();
248          let sm = sm.start();
249  
250          let sm = sm.pause();
251          assert_eq!(sm.current_state(), BotState::Paused);
252  
253          let sm = sm.resume();
254          assert_eq!(sm.current_state(), BotState::Running);
255      }
256  
257      #[test]
258      fn test_state_machine_error() {
259          let sm = StateMachine::new();
260          let sm = sm.initialize();
261          let sm = sm.fail("Initialization failed".to_string());
262  
263          assert_eq!(sm.current_state(), BotState::Error);
264  
265          let last_transition = sm.history().last().unwrap();
266          assert_eq!(last_transition.message, Some("Initialization failed".to_string()));
267      }
268  }