Skip to main content

syspulse_core/
lifecycle.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(rename_all = "snake_case")]
5pub enum LifecycleState {
6    Stopped,
7    Starting,
8    Running,
9    Stopping,
10    Failed,
11    Scheduled,
12}
13
14impl LifecycleState {
15    pub fn can_transition_to(&self, target: LifecycleState) -> bool {
16        use LifecycleState::*;
17        matches!(
18            (self, target),
19            (Stopped, Starting)
20                | (Stopped, Scheduled)
21                | (Starting, Running)
22                | (Starting, Failed)
23                | (Starting, Stopping)
24                | (Running, Stopping)
25                | (Running, Failed)
26                | (Stopping, Stopped)
27                | (Stopping, Failed)
28                | (Failed, Starting)
29                | (Failed, Stopped)
30                | (Scheduled, Starting)
31                | (Scheduled, Stopped)
32        )
33    }
34
35    pub fn transition_to(&self, target: LifecycleState) -> crate::error::Result<LifecycleState> {
36        if self.can_transition_to(target) {
37            Ok(target)
38        } else {
39            Err(crate::error::SyspulseError::InvalidStateTransition {
40                from: format!("{:?}", self),
41                to: format!("{:?}", target),
42            })
43        }
44    }
45
46    pub fn is_active(&self) -> bool {
47        matches!(self, Self::Starting | Self::Running | Self::Stopping)
48    }
49}
50
51impl std::fmt::Display for LifecycleState {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::Stopped => write!(f, "stopped"),
55            Self::Starting => write!(f, "starting"),
56            Self::Running => write!(f, "running"),
57            Self::Stopping => write!(f, "stopping"),
58            Self::Failed => write!(f, "failed"),
59            Self::Scheduled => write!(f, "scheduled"),
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use LifecycleState::*;
68
69    #[test]
70    fn valid_transitions() {
71        let valid = vec![
72            (Stopped, Starting),
73            (Stopped, Scheduled),
74            (Starting, Running),
75            (Starting, Failed),
76            (Starting, Stopping),
77            (Running, Stopping),
78            (Running, Failed),
79            (Stopping, Stopped),
80            (Stopping, Failed),
81            (Failed, Starting),
82            (Failed, Stopped),
83            (Scheduled, Starting),
84            (Scheduled, Stopped),
85        ];
86
87        for (from, to) in valid {
88            assert!(
89                from.can_transition_to(to),
90                "{:?} -> {:?} should be valid",
91                from,
92                to
93            );
94            assert!(
95                from.transition_to(to).is_ok(),
96                "{:?} -> {:?} transition_to should succeed",
97                from,
98                to
99            );
100        }
101    }
102
103    #[test]
104    fn invalid_transitions() {
105        let invalid = vec![
106            (Stopped, Running),
107            (Stopped, Stopping),
108            (Stopped, Failed),
109            (Starting, Stopped),
110            (Starting, Scheduled),
111            (Running, Starting),
112            (Running, Stopped),
113            (Running, Scheduled),
114            (Stopping, Starting),
115            (Stopping, Running),
116            (Stopping, Scheduled),
117            (Failed, Running),
118            (Failed, Stopping),
119            (Failed, Scheduled),
120            (Scheduled, Running),
121            (Scheduled, Stopping),
122            (Scheduled, Failed),
123        ];
124
125        for (from, to) in invalid {
126            assert!(
127                !from.can_transition_to(to),
128                "{:?} -> {:?} should be invalid",
129                from,
130                to
131            );
132            assert!(
133                from.transition_to(to).is_err(),
134                "{:?} -> {:?} transition_to should fail",
135                from,
136                to
137            );
138        }
139    }
140
141    #[test]
142    fn self_transitions_are_invalid() {
143        let all_states = vec![Stopped, Starting, Running, Stopping, Failed, Scheduled];
144        for state in all_states {
145            assert!(
146                !state.can_transition_to(state),
147                "{:?} -> {:?} self-transition should be invalid",
148                state,
149                state
150            );
151        }
152    }
153
154    #[test]
155    fn is_active() {
156        assert!(!Stopped.is_active());
157        assert!(Starting.is_active());
158        assert!(Running.is_active());
159        assert!(Stopping.is_active());
160        assert!(!Failed.is_active());
161        assert!(!Scheduled.is_active());
162    }
163
164    #[test]
165    fn display() {
166        assert_eq!(Stopped.to_string(), "stopped");
167        assert_eq!(Starting.to_string(), "starting");
168        assert_eq!(Running.to_string(), "running");
169        assert_eq!(Stopping.to_string(), "stopping");
170        assert_eq!(Failed.to_string(), "failed");
171        assert_eq!(Scheduled.to_string(), "scheduled");
172    }
173}