use std::time::{Duration, Instant};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::device::DeviceState;
use crate::error::Result;
#[async_trait]
pub trait DeviceLifecycle: Send + Sync {
fn state(&self) -> DeviceState;
fn is_operational(&self) -> bool {
self.state().is_operational()
}
fn can_accept_requests(&self) -> bool {
self.state().can_accept_requests()
}
async fn initialize(&mut self) -> Result<()>;
async fn start(&mut self) -> Result<()>;
async fn stop(&mut self) -> Result<()>;
async fn on_error(&mut self, _error: &crate::error::Error) -> Result<()> {
Ok(())
}
async fn recover(&mut self) -> Result<bool> {
Ok(false)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LifecycleEvent {
Initialized {
device_id: String,
timestamp: chrono::DateTime<chrono::Utc>,
},
Started {
device_id: String,
timestamp: chrono::DateTime<chrono::Utc>,
},
Stopped {
device_id: String,
timestamp: chrono::DateTime<chrono::Utc>,
reason: StopReason,
},
StateChanged {
device_id: String,
old_state: DeviceState,
new_state: DeviceState,
timestamp: chrono::DateTime<chrono::Utc>,
},
Error {
device_id: String,
error: String,
timestamp: chrono::DateTime<chrono::Utc>,
},
RecoveryAttempted {
device_id: String,
success: bool,
timestamp: chrono::DateTime<chrono::Utc>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum StopReason {
UserRequested,
Error,
Maintenance,
SystemShutdown,
Timeout,
}
#[derive(Debug, Clone)]
pub struct LifecycleStateMachine {
current_state: DeviceState,
last_transition: Option<Instant>,
error_count: u32,
max_retries: u32,
retry_delay: Duration,
}
impl LifecycleStateMachine {
pub fn new() -> Self {
Self {
current_state: DeviceState::Uninitialized,
last_transition: None,
error_count: 0,
max_retries: 3,
retry_delay: Duration::from_secs(1),
}
}
pub fn with_retries(mut self, max_retries: u32, retry_delay: Duration) -> Self {
self.max_retries = max_retries;
self.retry_delay = retry_delay;
self
}
pub fn state(&self) -> DeviceState {
self.current_state
}
pub fn can_transition_to(&self, target: DeviceState) -> bool {
match (self.current_state, target) {
(DeviceState::Uninitialized, DeviceState::Initializing) => true,
(DeviceState::Uninitialized, DeviceState::Error) => true,
(DeviceState::Initializing, DeviceState::Offline) => true,
(DeviceState::Initializing, DeviceState::Online) => true,
(DeviceState::Initializing, DeviceState::Error) => true,
(DeviceState::Offline, DeviceState::Online) => true,
(DeviceState::Offline, DeviceState::Error) => true,
(DeviceState::Online, DeviceState::Offline) => true,
(DeviceState::Online, DeviceState::ShuttingDown) => true,
(DeviceState::Online, DeviceState::Error) => true,
(DeviceState::ShuttingDown, DeviceState::Offline) => true,
(DeviceState::ShuttingDown, DeviceState::Error) => true,
(DeviceState::Error, DeviceState::Offline) => true,
(DeviceState::Error, DeviceState::Initializing) => true,
(DeviceState::Error, DeviceState::Uninitialized) => true,
_ => false,
}
}
pub fn transition_to(&mut self, target: DeviceState) -> Result<DeviceState> {
if !self.can_transition_to(target) {
return Err(crate::error::Error::Engine(format!(
"Invalid state transition: {:?} -> {:?}",
self.current_state, target
)));
}
let old_state = self.current_state;
self.current_state = target;
self.last_transition = Some(Instant::now());
if target != DeviceState::Error {
self.error_count = 0;
}
Ok(old_state)
}
pub fn record_error(&mut self) -> bool {
self.error_count += 1;
self.error_count > self.max_retries
}
pub fn error_count(&self) -> u32 {
self.error_count
}
pub fn reset_errors(&mut self) {
self.error_count = 0;
}
pub fn time_in_state(&self) -> Option<Duration> {
self.last_transition.map(|t| t.elapsed())
}
pub fn can_retry(&self) -> bool {
self.last_transition
.map(|t| t.elapsed() >= self.retry_delay)
.unwrap_or(true)
}
}
impl Default for LifecycleStateMachine {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
pub trait LifecycleHook: Send + Sync {
async fn before_init(&self) -> Result<()> {
Ok(())
}
async fn after_init(&self) -> Result<()> {
Ok(())
}
async fn before_start(&self) -> Result<()> {
Ok(())
}
async fn after_start(&self) -> Result<()> {
Ok(())
}
async fn before_stop(&self) -> Result<()> {
Ok(())
}
async fn after_stop(&self) -> Result<()> {
Ok(())
}
async fn on_error(&self, _error: &crate::error::Error) -> Result<()> {
Ok(())
}
}
pub struct NoOpLifecycleHook;
#[async_trait]
impl LifecycleHook for NoOpLifecycleHook {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lifecycle_state_machine_transitions() {
let mut sm = LifecycleStateMachine::new();
assert_eq!(sm.state(), DeviceState::Uninitialized);
sm.transition_to(DeviceState::Initializing).unwrap();
assert_eq!(sm.state(), DeviceState::Initializing);
sm.transition_to(DeviceState::Offline).unwrap();
assert_eq!(sm.state(), DeviceState::Offline);
sm.transition_to(DeviceState::Online).unwrap();
assert_eq!(sm.state(), DeviceState::Online);
}
#[test]
fn test_lifecycle_state_machine_invalid_transition() {
let mut sm = LifecycleStateMachine::new();
let result = sm.transition_to(DeviceState::Online);
assert!(result.is_err());
}
#[test]
fn test_lifecycle_state_machine_error_handling() {
let mut sm = LifecycleStateMachine::new().with_retries(3, Duration::from_millis(100));
assert!(!sm.record_error()); assert!(!sm.record_error()); assert!(!sm.record_error()); assert!(sm.record_error());
assert_eq!(sm.error_count(), 4);
sm.reset_errors();
assert_eq!(sm.error_count(), 0);
}
#[test]
fn test_lifecycle_valid_transitions() {
let sm = LifecycleStateMachine::new();
assert!(sm.can_transition_to(DeviceState::Initializing));
assert!(sm.can_transition_to(DeviceState::Error));
assert!(!sm.can_transition_to(DeviceState::Online));
assert!(!sm.can_transition_to(DeviceState::ShuttingDown));
}
}