meerkat_runtime/
runtime_state.rs1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10#[non_exhaustive]
11pub enum RuntimeState {
12 Initializing,
14 Idle,
16 Running,
18 Recovering,
20 Retired,
22 Stopped,
24 Destroyed,
26}
27
28impl RuntimeState {
29 pub fn is_terminal(&self) -> bool {
31 matches!(self, Self::Stopped | Self::Destroyed)
32 }
33
34 pub fn can_accept_input(&self) -> bool {
36 matches!(self, Self::Idle | Self::Running)
37 }
38
39 pub fn can_process_queue(&self) -> bool {
43 matches!(self, Self::Idle | Self::Retired)
44 }
45
46 pub fn can_transition_to(&self, next: &RuntimeState) -> bool {
48 use RuntimeState::{Destroyed, Idle, Initializing, Recovering, Retired, Running, Stopped};
49 matches!(
50 (self, next),
51 (Initializing, Idle | Stopped | Destroyed)
53 | (Idle, Running | Retired | Recovering | Stopped | Destroyed)
55 | (Running, Idle | Recovering | Retired | Stopped | Destroyed)
57 | (Recovering, Idle | Running | Stopped | Destroyed)
59 | (Retired, Running | Stopped | Destroyed)
61 )
62 }
63
64 pub fn transition(&mut self, next: RuntimeState) -> Result<(), RuntimeStateTransitionError> {
66 if self.can_transition_to(&next) {
67 *self = next;
68 Ok(())
69 } else {
70 Err(RuntimeStateTransitionError {
71 from: *self,
72 to: next,
73 })
74 }
75 }
76}
77
78impl std::fmt::Display for RuntimeState {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 Self::Initializing => write!(f, "initializing"),
82 Self::Idle => write!(f, "idle"),
83 Self::Running => write!(f, "running"),
84 Self::Recovering => write!(f, "recovering"),
85 Self::Retired => write!(f, "retired"),
86 Self::Stopped => write!(f, "stopped"),
87 Self::Destroyed => write!(f, "destroyed"),
88 }
89 }
90}
91
92#[derive(Debug, Clone, thiserror::Error)]
94#[error("Invalid runtime state transition: {from} -> {to}")]
95pub struct RuntimeStateTransitionError {
96 pub from: RuntimeState,
97 pub to: RuntimeState,
98}
99
100#[cfg(test)]
101#[allow(clippy::unwrap_used)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn terminal_states() {
107 assert!(RuntimeState::Stopped.is_terminal());
108 assert!(RuntimeState::Destroyed.is_terminal());
109
110 assert!(!RuntimeState::Initializing.is_terminal());
111 assert!(!RuntimeState::Idle.is_terminal());
112 assert!(!RuntimeState::Running.is_terminal());
113 assert!(!RuntimeState::Recovering.is_terminal());
114 assert!(!RuntimeState::Retired.is_terminal());
115 }
116
117 #[test]
118 fn can_accept_input() {
119 assert!(RuntimeState::Idle.can_accept_input());
120 assert!(RuntimeState::Running.can_accept_input());
121
122 assert!(!RuntimeState::Initializing.can_accept_input());
123 assert!(!RuntimeState::Recovering.can_accept_input());
124 assert!(!RuntimeState::Retired.can_accept_input());
125 assert!(!RuntimeState::Stopped.can_accept_input());
126 assert!(!RuntimeState::Destroyed.can_accept_input());
127 }
128
129 #[test]
130 fn can_process_queue() {
131 assert!(RuntimeState::Idle.can_process_queue());
132 assert!(RuntimeState::Retired.can_process_queue());
133
134 assert!(!RuntimeState::Initializing.can_process_queue());
135 assert!(!RuntimeState::Running.can_process_queue());
136 assert!(!RuntimeState::Recovering.can_process_queue());
137 assert!(!RuntimeState::Stopped.can_process_queue());
138 assert!(!RuntimeState::Destroyed.can_process_queue());
139 }
140
141 #[test]
143 fn initializing_valid_transitions() {
144 let s = RuntimeState::Initializing;
145 assert!(s.can_transition_to(&RuntimeState::Idle));
146 assert!(s.can_transition_to(&RuntimeState::Stopped));
147 assert!(s.can_transition_to(&RuntimeState::Destroyed));
148
149 assert!(!s.can_transition_to(&RuntimeState::Running));
151 assert!(!s.can_transition_to(&RuntimeState::Recovering));
152 assert!(!s.can_transition_to(&RuntimeState::Retired));
153 assert!(!s.can_transition_to(&RuntimeState::Initializing));
154 }
155
156 #[test]
157 fn idle_valid_transitions() {
158 let s = RuntimeState::Idle;
159 assert!(s.can_transition_to(&RuntimeState::Running));
160 assert!(s.can_transition_to(&RuntimeState::Retired));
161 assert!(s.can_transition_to(&RuntimeState::Recovering));
162 assert!(s.can_transition_to(&RuntimeState::Stopped));
163 assert!(s.can_transition_to(&RuntimeState::Destroyed));
164
165 assert!(!s.can_transition_to(&RuntimeState::Initializing));
167 assert!(!s.can_transition_to(&RuntimeState::Idle));
168 }
169
170 #[test]
171 fn running_valid_transitions() {
172 let s = RuntimeState::Running;
173 assert!(s.can_transition_to(&RuntimeState::Idle));
174 assert!(s.can_transition_to(&RuntimeState::Recovering));
175 assert!(s.can_transition_to(&RuntimeState::Retired));
176 assert!(s.can_transition_to(&RuntimeState::Stopped));
177 assert!(s.can_transition_to(&RuntimeState::Destroyed));
178
179 assert!(!s.can_transition_to(&RuntimeState::Initializing));
181 assert!(!s.can_transition_to(&RuntimeState::Running));
182 }
183
184 #[test]
185 fn recovering_valid_transitions() {
186 let s = RuntimeState::Recovering;
187 assert!(s.can_transition_to(&RuntimeState::Idle));
188 assert!(s.can_transition_to(&RuntimeState::Running));
189 assert!(s.can_transition_to(&RuntimeState::Stopped));
190 assert!(s.can_transition_to(&RuntimeState::Destroyed));
191
192 assert!(!s.can_transition_to(&RuntimeState::Initializing));
194 assert!(!s.can_transition_to(&RuntimeState::Recovering));
195 assert!(!s.can_transition_to(&RuntimeState::Retired));
196 }
197
198 #[test]
199 fn retired_valid_transitions() {
200 let s = RuntimeState::Retired;
201 assert!(s.can_transition_to(&RuntimeState::Running));
202 assert!(s.can_transition_to(&RuntimeState::Stopped));
203 assert!(s.can_transition_to(&RuntimeState::Destroyed));
204
205 assert!(!s.can_transition_to(&RuntimeState::Initializing));
207 assert!(!s.can_transition_to(&RuntimeState::Idle));
208 assert!(!s.can_transition_to(&RuntimeState::Recovering));
209 assert!(!s.can_transition_to(&RuntimeState::Retired));
210 }
211
212 #[test]
213 fn stopped_is_terminal() {
214 let s = RuntimeState::Stopped;
215 assert!(!s.can_transition_to(&RuntimeState::Initializing));
216 assert!(!s.can_transition_to(&RuntimeState::Idle));
217 assert!(!s.can_transition_to(&RuntimeState::Running));
218 assert!(!s.can_transition_to(&RuntimeState::Recovering));
219 assert!(!s.can_transition_to(&RuntimeState::Retired));
220 assert!(!s.can_transition_to(&RuntimeState::Stopped));
221 assert!(!s.can_transition_to(&RuntimeState::Destroyed));
222 }
223
224 #[test]
225 fn destroyed_is_terminal() {
226 let s = RuntimeState::Destroyed;
227 assert!(!s.can_transition_to(&RuntimeState::Initializing));
228 assert!(!s.can_transition_to(&RuntimeState::Idle));
229 assert!(!s.can_transition_to(&RuntimeState::Running));
230 assert!(!s.can_transition_to(&RuntimeState::Recovering));
231 assert!(!s.can_transition_to(&RuntimeState::Retired));
232 assert!(!s.can_transition_to(&RuntimeState::Stopped));
233 assert!(!s.can_transition_to(&RuntimeState::Destroyed));
234 }
235
236 #[test]
237 fn transition_success() {
238 let mut state = RuntimeState::Initializing;
239 assert!(state.transition(RuntimeState::Idle).is_ok());
240 assert_eq!(state, RuntimeState::Idle);
241
242 assert!(state.transition(RuntimeState::Running).is_ok());
243 assert_eq!(state, RuntimeState::Running);
244
245 assert!(state.transition(RuntimeState::Idle).is_ok());
246 assert_eq!(state, RuntimeState::Idle);
247 }
248
249 #[test]
250 fn transition_failure() {
251 let mut state = RuntimeState::Stopped;
252 let result = state.transition(RuntimeState::Idle);
253 assert!(result.is_err());
254 assert_eq!(state, RuntimeState::Stopped); }
256
257 #[test]
258 fn serde_roundtrip_all_states() {
259 for state in [
260 RuntimeState::Initializing,
261 RuntimeState::Idle,
262 RuntimeState::Running,
263 RuntimeState::Recovering,
264 RuntimeState::Retired,
265 RuntimeState::Stopped,
266 RuntimeState::Destroyed,
267 ] {
268 let json = serde_json::to_value(state).unwrap();
269 let parsed: RuntimeState = serde_json::from_value(json).unwrap();
270 assert_eq!(state, parsed);
271 }
272 }
273
274 #[test]
275 fn display() {
276 assert_eq!(RuntimeState::Idle.to_string(), "idle");
277 assert_eq!(RuntimeState::Running.to_string(), "running");
278 assert_eq!(RuntimeState::Destroyed.to_string(), "destroyed");
279 }
280}