1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[repr(u8)]
16pub enum LifecycleState {
17 Unconfigured = 1,
19 Inactive = 2,
21 Active = 3,
23 Finalized = 4,
25 ErrorProcessing = 5,
27}
28
29impl LifecycleState {
30 pub const fn is_terminal(&self) -> bool {
32 matches!(self, Self::Finalized)
33 }
34
35 pub const fn is_primary(&self) -> bool {
40 !matches!(self, Self::ErrorProcessing)
41 }
42
43 pub const fn from_u8(value: u8) -> Option<Self> {
45 match value {
46 1 => Some(Self::Unconfigured),
47 2 => Some(Self::Inactive),
48 3 => Some(Self::Active),
49 4 => Some(Self::Finalized),
50 5 => Some(Self::ErrorProcessing),
51 _ => None,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61#[repr(u8)]
62pub enum LifecycleTransition {
63 Configure = 1,
65 Activate = 2,
67 Deactivate = 3,
69 Cleanup = 4,
71 ShutdownUnconfigured = 5,
73 ShutdownInactive = 6,
75 ShutdownActive = 7,
77 ErrorRecovery = 8,
79}
80
81impl LifecycleTransition {
82 pub fn from_shorthand(state: LifecycleState, name: &str) -> Option<Self> {
87 match name {
88 "configure" => Some(Self::Configure),
89 "activate" => Some(Self::Activate),
90 "deactivate" => Some(Self::Deactivate),
91 "cleanup" => Some(Self::Cleanup),
92 "shutdown" => match state {
93 LifecycleState::Unconfigured => Some(Self::ShutdownUnconfigured),
94 LifecycleState::Inactive => Some(Self::ShutdownInactive),
95 LifecycleState::Active => Some(Self::ShutdownActive),
96 _ => None,
97 },
98 "error_recovery" => Some(Self::ErrorRecovery),
99 _ => None,
100 }
101 }
102
103 pub const fn from_u8(value: u8) -> Option<Self> {
105 match value {
106 1 => Some(Self::Configure),
107 2 => Some(Self::Activate),
108 3 => Some(Self::Deactivate),
109 4 => Some(Self::Cleanup),
110 5 => Some(Self::ShutdownUnconfigured),
111 6 => Some(Self::ShutdownInactive),
112 7 => Some(Self::ShutdownActive),
113 8 => Some(Self::ErrorRecovery),
114 _ => None,
115 }
116 }
117
118 pub const fn source_state(&self) -> LifecycleState {
120 match self {
121 Self::Configure => LifecycleState::Unconfigured,
122 Self::Activate => LifecycleState::Inactive,
123 Self::Deactivate => LifecycleState::Active,
124 Self::Cleanup => LifecycleState::Inactive,
125 Self::ShutdownUnconfigured => LifecycleState::Unconfigured,
126 Self::ShutdownInactive => LifecycleState::Inactive,
127 Self::ShutdownActive => LifecycleState::Active,
128 Self::ErrorRecovery => LifecycleState::ErrorProcessing,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
138#[repr(u8)]
139pub enum TransitionResult {
140 Success = 0,
142 Failure = 1,
144 Error = 2,
146}
147
148impl TransitionResult {
149 pub const fn from_u8(value: u8) -> Option<Self> {
151 match value {
152 0 => Some(Self::Success),
153 1 => Some(Self::Failure),
154 2 => Some(Self::Error),
155 _ => None,
156 }
157 }
158}
159
160pub const fn can_transition(state: LifecycleState, transition: LifecycleTransition) -> bool {
162 matches!(
163 (state, transition),
164 (LifecycleState::Unconfigured, LifecycleTransition::Configure)
165 | (
166 LifecycleState::Unconfigured,
167 LifecycleTransition::ShutdownUnconfigured
168 )
169 | (LifecycleState::Inactive, LifecycleTransition::Activate)
170 | (LifecycleState::Inactive, LifecycleTransition::Cleanup)
171 | (
172 LifecycleState::Inactive,
173 LifecycleTransition::ShutdownInactive
174 )
175 | (LifecycleState::Active, LifecycleTransition::Deactivate)
176 | (LifecycleState::Active, LifecycleTransition::ShutdownActive)
177 | (
178 LifecycleState::ErrorProcessing,
179 LifecycleTransition::ErrorRecovery
180 )
181 )
182}
183
184pub const fn apply_transition(
191 state: LifecycleState,
192 transition: LifecycleTransition,
193 result: TransitionResult,
194) -> LifecycleState {
195 match result {
196 TransitionResult::Error => LifecycleState::ErrorProcessing,
197 TransitionResult::Success => match transition {
198 LifecycleTransition::Configure => LifecycleState::Inactive,
199 LifecycleTransition::Activate => LifecycleState::Active,
200 LifecycleTransition::Deactivate => LifecycleState::Inactive,
201 LifecycleTransition::Cleanup => LifecycleState::Unconfigured,
202 LifecycleTransition::ShutdownUnconfigured
203 | LifecycleTransition::ShutdownInactive
204 | LifecycleTransition::ShutdownActive => LifecycleState::Finalized,
205 LifecycleTransition::ErrorRecovery => LifecycleState::Unconfigured,
206 },
207 TransitionResult::Failure => {
208 match transition {
212 LifecycleTransition::ErrorRecovery => LifecycleState::ErrorProcessing,
213 _ => state,
214 }
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_lifecycle_state_properties() {
225 assert!(!LifecycleState::Unconfigured.is_terminal());
226 assert!(!LifecycleState::Inactive.is_terminal());
227 assert!(!LifecycleState::Active.is_terminal());
228 assert!(LifecycleState::Finalized.is_terminal());
229 assert!(!LifecycleState::ErrorProcessing.is_terminal());
230
231 assert!(LifecycleState::Unconfigured.is_primary());
232 assert!(LifecycleState::Inactive.is_primary());
233 assert!(LifecycleState::Active.is_primary());
234 assert!(LifecycleState::Finalized.is_primary());
235 assert!(!LifecycleState::ErrorProcessing.is_primary());
236 }
237
238 #[test]
239 fn test_state_from_u8() {
240 assert_eq!(
241 LifecycleState::from_u8(1),
242 Some(LifecycleState::Unconfigured)
243 );
244 assert_eq!(LifecycleState::from_u8(2), Some(LifecycleState::Inactive));
245 assert_eq!(LifecycleState::from_u8(3), Some(LifecycleState::Active));
246 assert_eq!(LifecycleState::from_u8(4), Some(LifecycleState::Finalized));
247 assert_eq!(
248 LifecycleState::from_u8(5),
249 Some(LifecycleState::ErrorProcessing)
250 );
251 assert_eq!(LifecycleState::from_u8(0), None);
252 assert_eq!(LifecycleState::from_u8(6), None);
253 }
254
255 #[test]
256 fn test_transition_from_u8() {
257 assert_eq!(
258 LifecycleTransition::from_u8(1),
259 Some(LifecycleTransition::Configure)
260 );
261 assert_eq!(
262 LifecycleTransition::from_u8(8),
263 Some(LifecycleTransition::ErrorRecovery)
264 );
265 assert_eq!(LifecycleTransition::from_u8(0), None);
266 assert_eq!(LifecycleTransition::from_u8(9), None);
267 }
268
269 #[test]
270 fn test_transition_result_from_u8() {
271 assert_eq!(
272 TransitionResult::from_u8(0),
273 Some(TransitionResult::Success)
274 );
275 assert_eq!(
276 TransitionResult::from_u8(1),
277 Some(TransitionResult::Failure)
278 );
279 assert_eq!(TransitionResult::from_u8(2), Some(TransitionResult::Error));
280 assert_eq!(TransitionResult::from_u8(3), None);
281 }
282
283 #[test]
284 fn test_valid_transitions() {
285 assert!(can_transition(
287 LifecycleState::Unconfigured,
288 LifecycleTransition::Configure
289 ));
290 assert!(can_transition(
291 LifecycleState::Unconfigured,
292 LifecycleTransition::ShutdownUnconfigured
293 ));
294 assert!(!can_transition(
295 LifecycleState::Unconfigured,
296 LifecycleTransition::Activate
297 ));
298 assert!(!can_transition(
299 LifecycleState::Unconfigured,
300 LifecycleTransition::Deactivate
301 ));
302
303 assert!(can_transition(
305 LifecycleState::Inactive,
306 LifecycleTransition::Activate
307 ));
308 assert!(can_transition(
309 LifecycleState::Inactive,
310 LifecycleTransition::Cleanup
311 ));
312 assert!(can_transition(
313 LifecycleState::Inactive,
314 LifecycleTransition::ShutdownInactive
315 ));
316 assert!(!can_transition(
317 LifecycleState::Inactive,
318 LifecycleTransition::Configure
319 ));
320 assert!(!can_transition(
321 LifecycleState::Inactive,
322 LifecycleTransition::Deactivate
323 ));
324
325 assert!(can_transition(
327 LifecycleState::Active,
328 LifecycleTransition::Deactivate
329 ));
330 assert!(can_transition(
331 LifecycleState::Active,
332 LifecycleTransition::ShutdownActive
333 ));
334 assert!(!can_transition(
335 LifecycleState::Active,
336 LifecycleTransition::Activate
337 ));
338 assert!(!can_transition(
339 LifecycleState::Active,
340 LifecycleTransition::Configure
341 ));
342
343 assert!(!can_transition(
345 LifecycleState::Finalized,
346 LifecycleTransition::Configure
347 ));
348 assert!(!can_transition(
349 LifecycleState::Finalized,
350 LifecycleTransition::ShutdownUnconfigured
351 ));
352
353 assert!(can_transition(
355 LifecycleState::ErrorProcessing,
356 LifecycleTransition::ErrorRecovery
357 ));
358 assert!(!can_transition(
359 LifecycleState::ErrorProcessing,
360 LifecycleTransition::Configure
361 ));
362 }
363
364 #[test]
365 fn test_apply_transition_success() {
366 assert_eq!(
367 apply_transition(
368 LifecycleState::Unconfigured,
369 LifecycleTransition::Configure,
370 TransitionResult::Success
371 ),
372 LifecycleState::Inactive
373 );
374 assert_eq!(
375 apply_transition(
376 LifecycleState::Inactive,
377 LifecycleTransition::Activate,
378 TransitionResult::Success
379 ),
380 LifecycleState::Active
381 );
382 assert_eq!(
383 apply_transition(
384 LifecycleState::Active,
385 LifecycleTransition::Deactivate,
386 TransitionResult::Success
387 ),
388 LifecycleState::Inactive
389 );
390 assert_eq!(
391 apply_transition(
392 LifecycleState::Inactive,
393 LifecycleTransition::Cleanup,
394 TransitionResult::Success
395 ),
396 LifecycleState::Unconfigured
397 );
398 assert_eq!(
399 apply_transition(
400 LifecycleState::Unconfigured,
401 LifecycleTransition::ShutdownUnconfigured,
402 TransitionResult::Success
403 ),
404 LifecycleState::Finalized
405 );
406 assert_eq!(
407 apply_transition(
408 LifecycleState::Inactive,
409 LifecycleTransition::ShutdownInactive,
410 TransitionResult::Success
411 ),
412 LifecycleState::Finalized
413 );
414 assert_eq!(
415 apply_transition(
416 LifecycleState::Active,
417 LifecycleTransition::ShutdownActive,
418 TransitionResult::Success
419 ),
420 LifecycleState::Finalized
421 );
422 assert_eq!(
423 apply_transition(
424 LifecycleState::ErrorProcessing,
425 LifecycleTransition::ErrorRecovery,
426 TransitionResult::Success
427 ),
428 LifecycleState::Unconfigured
429 );
430 }
431
432 #[test]
433 fn test_apply_transition_failure_rolls_back() {
434 assert_eq!(
436 apply_transition(
437 LifecycleState::Unconfigured,
438 LifecycleTransition::Configure,
439 TransitionResult::Failure
440 ),
441 LifecycleState::Unconfigured
442 );
443 assert_eq!(
445 apply_transition(
446 LifecycleState::Inactive,
447 LifecycleTransition::Activate,
448 TransitionResult::Failure
449 ),
450 LifecycleState::Inactive
451 );
452 assert_eq!(
454 apply_transition(
455 LifecycleState::Active,
456 LifecycleTransition::Deactivate,
457 TransitionResult::Failure
458 ),
459 LifecycleState::Active
460 );
461 assert_eq!(
463 apply_transition(
464 LifecycleState::ErrorProcessing,
465 LifecycleTransition::ErrorRecovery,
466 TransitionResult::Failure
467 ),
468 LifecycleState::ErrorProcessing
469 );
470 }
471
472 #[test]
473 fn test_apply_transition_error_goes_to_error_processing() {
474 assert_eq!(
475 apply_transition(
476 LifecycleState::Unconfigured,
477 LifecycleTransition::Configure,
478 TransitionResult::Error
479 ),
480 LifecycleState::ErrorProcessing
481 );
482 assert_eq!(
483 apply_transition(
484 LifecycleState::Inactive,
485 LifecycleTransition::Activate,
486 TransitionResult::Error
487 ),
488 LifecycleState::ErrorProcessing
489 );
490 assert_eq!(
491 apply_transition(
492 LifecycleState::Active,
493 LifecycleTransition::ShutdownActive,
494 TransitionResult::Error
495 ),
496 LifecycleState::ErrorProcessing
497 );
498 }
499
500 #[test]
501 fn test_shutdown_shorthand_disambiguation() {
502 assert_eq!(
503 LifecycleTransition::from_shorthand(LifecycleState::Unconfigured, "shutdown"),
504 Some(LifecycleTransition::ShutdownUnconfigured)
505 );
506 assert_eq!(
507 LifecycleTransition::from_shorthand(LifecycleState::Inactive, "shutdown"),
508 Some(LifecycleTransition::ShutdownInactive)
509 );
510 assert_eq!(
511 LifecycleTransition::from_shorthand(LifecycleState::Active, "shutdown"),
512 Some(LifecycleTransition::ShutdownActive)
513 );
514 assert_eq!(
516 LifecycleTransition::from_shorthand(LifecycleState::Finalized, "shutdown"),
517 None
518 );
519 assert_eq!(
520 LifecycleTransition::from_shorthand(LifecycleState::ErrorProcessing, "shutdown"),
521 None
522 );
523 }
524
525 #[test]
526 fn test_shorthand_other_transitions() {
527 assert_eq!(
528 LifecycleTransition::from_shorthand(LifecycleState::Unconfigured, "configure"),
529 Some(LifecycleTransition::Configure)
530 );
531 assert_eq!(
532 LifecycleTransition::from_shorthand(LifecycleState::Active, "deactivate"),
533 Some(LifecycleTransition::Deactivate)
534 );
535 assert_eq!(
536 LifecycleTransition::from_shorthand(LifecycleState::Inactive, "cleanup"),
537 Some(LifecycleTransition::Cleanup)
538 );
539 assert_eq!(
540 LifecycleTransition::from_shorthand(LifecycleState::ErrorProcessing, "error_recovery"),
541 Some(LifecycleTransition::ErrorRecovery)
542 );
543 assert_eq!(
544 LifecycleTransition::from_shorthand(LifecycleState::Active, "unknown"),
545 None
546 );
547 }
548
549 #[test]
550 fn test_transition_source_state() {
551 assert_eq!(
552 LifecycleTransition::Configure.source_state(),
553 LifecycleState::Unconfigured
554 );
555 assert_eq!(
556 LifecycleTransition::Activate.source_state(),
557 LifecycleState::Inactive
558 );
559 assert_eq!(
560 LifecycleTransition::Deactivate.source_state(),
561 LifecycleState::Active
562 );
563 assert_eq!(
564 LifecycleTransition::Cleanup.source_state(),
565 LifecycleState::Inactive
566 );
567 assert_eq!(
568 LifecycleTransition::ShutdownUnconfigured.source_state(),
569 LifecycleState::Unconfigured
570 );
571 assert_eq!(
572 LifecycleTransition::ShutdownInactive.source_state(),
573 LifecycleState::Inactive
574 );
575 assert_eq!(
576 LifecycleTransition::ShutdownActive.source_state(),
577 LifecycleState::Active
578 );
579 assert_eq!(
580 LifecycleTransition::ErrorRecovery.source_state(),
581 LifecycleState::ErrorProcessing
582 );
583 }
584
585 #[test]
586 fn test_full_lifecycle_happy_path() {
587 let mut state = LifecycleState::Unconfigured;
588
589 assert!(can_transition(state, LifecycleTransition::Configure));
591 state = apply_transition(
592 state,
593 LifecycleTransition::Configure,
594 TransitionResult::Success,
595 );
596 assert_eq!(state, LifecycleState::Inactive);
597
598 assert!(can_transition(state, LifecycleTransition::Activate));
600 state = apply_transition(
601 state,
602 LifecycleTransition::Activate,
603 TransitionResult::Success,
604 );
605 assert_eq!(state, LifecycleState::Active);
606
607 assert!(can_transition(state, LifecycleTransition::Deactivate));
609 state = apply_transition(
610 state,
611 LifecycleTransition::Deactivate,
612 TransitionResult::Success,
613 );
614 assert_eq!(state, LifecycleState::Inactive);
615
616 assert!(can_transition(state, LifecycleTransition::ShutdownInactive));
618 state = apply_transition(
619 state,
620 LifecycleTransition::ShutdownInactive,
621 TransitionResult::Success,
622 );
623 assert_eq!(state, LifecycleState::Finalized);
624 assert!(state.is_terminal());
625 }
626
627 #[test]
628 fn test_error_recovery_path() {
629 let mut state = LifecycleState::Unconfigured;
630
631 state = apply_transition(
633 state,
634 LifecycleTransition::Configure,
635 TransitionResult::Error,
636 );
637 assert_eq!(state, LifecycleState::ErrorProcessing);
638 assert!(!state.is_primary());
639
640 assert!(can_transition(state, LifecycleTransition::ErrorRecovery));
642 state = apply_transition(
643 state,
644 LifecycleTransition::ErrorRecovery,
645 TransitionResult::Success,
646 );
647 assert_eq!(state, LifecycleState::Unconfigured);
648 }
649}