meerkat_runtime/
runtime_state.rs1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11#[non_exhaustive]
12pub enum RuntimeState {
13 Initializing,
15 Idle,
17 Attached,
19 Running,
21 Retired,
23 Stopped,
27 Destroyed,
29}
30
31impl std::fmt::Display for RuntimeState {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 match self {
34 Self::Initializing => write!(f, "initializing"),
35 Self::Idle => write!(f, "idle"),
36 Self::Attached => write!(f, "attached"),
37 Self::Running => write!(f, "running"),
38 Self::Retired => write!(f, "retired"),
39 Self::Stopped => write!(f, "stopped"),
40 Self::Destroyed => write!(f, "destroyed"),
41 }
42 }
43}
44
45#[derive(Debug, Clone, thiserror::Error)]
47#[error("Invalid runtime state transition: {from} -> {to}")]
48pub struct RuntimeStateTransitionError {
49 pub from: RuntimeState,
50 pub to: RuntimeState,
51}
52
53#[cfg(test)]
54#[allow(clippy::unwrap_used)]
55mod tests {
56 use super::*;
57 use crate::meerkat_machine::{self, dsl};
58
59 #[test]
60 fn lifecycle_facts_come_from_machine_authority() {
61 let stopped = meerkat_machine::classify_runtime_lifecycle_state(RuntimeState::Stopped)
62 .expect("stopped classification");
63 assert_eq!(
64 stopped.terminality,
65 dsl::RuntimeLifecycleTerminality::NonTerminal
66 );
67 assert_eq!(
68 stopped.ingress_admission,
69 dsl::RuntimeIngressAdmission::NotReady
70 );
71
72 let destroyed = meerkat_machine::classify_runtime_lifecycle_state(RuntimeState::Destroyed)
73 .expect("destroyed classification");
74 assert_eq!(
75 destroyed.terminality,
76 dsl::RuntimeLifecycleTerminality::Terminal
77 );
78 assert_eq!(
79 destroyed.ingress_admission,
80 dsl::RuntimeIngressAdmission::Destroyed
81 );
82
83 let idle = meerkat_machine::classify_runtime_lifecycle_state(RuntimeState::Idle)
84 .expect("idle classification");
85 assert!(idle.can_accept_input());
86 assert!(idle.can_process_queue());
87 assert!(idle.can_prepare_run());
88
89 let running = meerkat_machine::classify_runtime_lifecycle_state(RuntimeState::Running)
90 .expect("running classification");
91 assert!(running.can_accept_input());
92 assert!(!running.can_process_queue());
93 assert!(!running.can_prepare_run());
94
95 let running_without_binding =
96 meerkat_machine::classify_runtime_loop_queue_admission(RuntimeState::Running, false)
97 .expect("running without binding queue admission");
98 assert!(!running_without_binding.can_process_queue());
99 assert_eq!(
100 running_without_binding.run_binding,
101 dsl::RuntimeLoopRunBinding::Blocked
102 );
103
104 let running_with_binding =
105 meerkat_machine::classify_runtime_loop_queue_admission(RuntimeState::Running, true)
106 .expect("running with binding queue admission");
107 assert!(running_with_binding.can_process_queue());
108 assert_eq!(
109 running_with_binding.run_binding,
110 dsl::RuntimeLoopRunBinding::UsePrebound
111 );
112
113 let idle_queue =
114 meerkat_machine::classify_runtime_loop_queue_admission(RuntimeState::Idle, false)
115 .expect("idle queue admission");
116 assert!(idle_queue.can_process_queue());
117 assert_eq!(
118 idle_queue.run_binding,
119 dsl::RuntimeLoopRunBinding::AllocateNew
120 );
121
122 assert_eq!(
123 meerkat_machine::classify_runtime_lifecycle_durable_state(RuntimeState::Attached)
124 .expect("attached durability classification"),
125 RuntimeState::Idle,
126 "generated durability classification owns the process-local Attached recovery projection"
127 );
128 assert_eq!(
129 meerkat_machine::classify_runtime_lifecycle_durable_state(RuntimeState::Running)
130 .expect("running durability classification"),
131 RuntimeState::Running
132 );
133 }
134
135 #[test]
136 fn serde_roundtrip_all_states() {
137 for state in [
138 RuntimeState::Initializing,
139 RuntimeState::Idle,
140 RuntimeState::Attached,
141 RuntimeState::Running,
142 RuntimeState::Retired,
143 RuntimeState::Stopped,
144 RuntimeState::Destroyed,
145 ] {
146 let json = serde_json::to_value(state).unwrap();
147 let parsed: RuntimeState = serde_json::from_value(json).unwrap();
148 assert_eq!(state, parsed);
149 }
150 }
151
152 #[test]
153 fn display() {
154 assert_eq!(RuntimeState::Idle.to_string(), "idle");
155 assert_eq!(RuntimeState::Attached.to_string(), "attached");
156 assert_eq!(RuntimeState::Running.to_string(), "running");
157 assert_eq!(RuntimeState::Destroyed.to_string(), "destroyed");
158 }
159}