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}