1use serde::{Deserialize, Serialize};
2use std::str::FromStr;
3
4#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
5#[serde(rename_all = "snake_case")]
6pub enum JobStage {
7 Queued,
8 Scheduled,
9 Running,
10 Completed,
11}
12
13impl JobStage {
14 #[must_use]
15 pub fn from_db_value(raw_stage: &str) -> Option<Self> {
16 match raw_stage {
17 "queued" => Some(Self::Queued),
18 "scheduled" => Some(Self::Scheduled),
19 "running" => Some(Self::Running),
20 "completed" => Some(Self::Completed),
21 _ => None,
22 }
23 }
24
25 #[must_use]
26 pub const fn as_db_value(self) -> &'static str {
27 match self {
28 Self::Queued => "queued",
29 Self::Scheduled => "scheduled",
30 Self::Running => "running",
31 Self::Completed => "completed",
32 }
33 }
34}
35
36impl FromStr for JobStage {
37 type Err = ();
38
39 fn from_str(raw_stage: &str) -> Result<Self, Self::Err> {
40 Self::from_db_value(raw_stage).ok_or(())
41 }
42}
43
44#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
45#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
46pub enum JobStatus {
47 Pending,
48 Leased,
49 Succeeded,
50 DeadLettered,
51 Canceled,
52}
53
54impl JobStatus {
55 #[must_use]
56 pub fn from_db_value(raw_status: &str) -> Option<Self> {
57 match raw_status {
58 "PENDING" => Some(Self::Pending),
59 "LEASED" => Some(Self::Leased),
60 "SUCCEEDED" => Some(Self::Succeeded),
61 "DEAD_LETTERED" => Some(Self::DeadLettered),
62 "CANCELED" => Some(Self::Canceled),
63 _ => None,
64 }
65 }
66
67 #[must_use]
68 pub const fn as_db_value(self) -> &'static str {
69 match self {
70 Self::Pending => "PENDING",
71 Self::Leased => "LEASED",
72 Self::Succeeded => "SUCCEEDED",
73 Self::DeadLettered => "DEAD_LETTERED",
74 Self::Canceled => "CANCELED",
75 }
76 }
77}
78
79impl FromStr for JobStatus {
80 type Err = ();
81
82 fn from_str(raw_status: &str) -> Result<Self, Self::Err> {
83 Self::from_db_value(raw_status).ok_or(())
84 }
85}
86
87#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
88#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
89pub enum JobEventType {
90 Enqueued,
91 Leased,
92 Heartbeat,
93 StageChanged,
94 Progress,
95 RetryScheduled,
96 Succeeded,
97 Failed,
98 DeadLettered,
99 Canceled,
100 Requeued,
101}
102
103impl JobEventType {
104 #[must_use]
105 pub fn from_db_value(raw_type: &str) -> Option<Self> {
106 match raw_type {
107 "ENQUEUED" => Some(Self::Enqueued),
108 "LEASED" => Some(Self::Leased),
109 "HEARTBEAT" => Some(Self::Heartbeat),
110 "STAGE_CHANGED" => Some(Self::StageChanged),
111 "PROGRESS" => Some(Self::Progress),
112 "RETRY_SCHEDULED" => Some(Self::RetryScheduled),
113 "SUCCEEDED" => Some(Self::Succeeded),
114 "FAILED" => Some(Self::Failed),
115 "DEAD_LETTERED" => Some(Self::DeadLettered),
116 "CANCELED" => Some(Self::Canceled),
117 "REQUEUED" => Some(Self::Requeued),
118 _ => None,
119 }
120 }
121
122 #[must_use]
123 pub const fn as_db_value(self) -> &'static str {
124 match self {
125 Self::Enqueued => "ENQUEUED",
126 Self::Leased => "LEASED",
127 Self::Heartbeat => "HEARTBEAT",
128 Self::StageChanged => "STAGE_CHANGED",
129 Self::Progress => "PROGRESS",
130 Self::RetryScheduled => "RETRY_SCHEDULED",
131 Self::Succeeded => "SUCCEEDED",
132 Self::Failed => "FAILED",
133 Self::DeadLettered => "DEAD_LETTERED",
134 Self::Canceled => "CANCELED",
135 Self::Requeued => "REQUEUED",
136 }
137 }
138}
139
140impl FromStr for JobEventType {
141 type Err = ();
142
143 fn from_str(raw_type: &str) -> Result<Self, Self::Err> {
144 Self::from_db_value(raw_type).ok_or(())
145 }
146}
147
148#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
149#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
150pub enum JobFailureKind {
151 Retryable,
152 Terminal,
153 Timeout,
154 LeaseExpired,
155 Panicked,
156}
157
158impl JobFailureKind {
159 #[must_use]
160 pub const fn as_db_value(self) -> &'static str {
161 match self {
162 Self::Retryable => "RETRYABLE",
163 Self::Terminal => "TERMINAL",
164 Self::Timeout => "TIMEOUT",
165 Self::LeaseExpired => "LEASE_EXPIRED",
166 Self::Panicked => "PANICKED",
167 }
168 }
169}
170
171#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
172#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
173pub enum WorkflowRunStatus {
174 Running,
175 WaitingForExternal,
176 Succeeded,
177 CompletedWithErrors,
178 Canceled,
179}
180
181impl WorkflowRunStatus {
182 #[must_use]
183 pub fn from_db_value(raw_status: &str) -> Option<Self> {
184 match raw_status {
185 "RUNNING" => Some(Self::Running),
186 "WAITING_FOR_EXTERNAL" => Some(Self::WaitingForExternal),
187 "SUCCEEDED" => Some(Self::Succeeded),
188 "COMPLETED_WITH_ERRORS" => Some(Self::CompletedWithErrors),
189 "CANCELED" => Some(Self::Canceled),
190 _ => None,
191 }
192 }
193
194 #[must_use]
195 pub const fn as_db_value(self) -> &'static str {
196 match self {
197 Self::Running => "RUNNING",
198 Self::WaitingForExternal => "WAITING_FOR_EXTERNAL",
199 Self::Succeeded => "SUCCEEDED",
200 Self::CompletedWithErrors => "COMPLETED_WITH_ERRORS",
201 Self::Canceled => "CANCELED",
202 }
203 }
204}
205
206impl FromStr for WorkflowRunStatus {
207 type Err = ();
208
209 fn from_str(raw_status: &str) -> Result<Self, Self::Err> {
210 Self::from_db_value(raw_status).ok_or(())
211 }
212}
213
214#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
215#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
216pub enum WorkflowStepStatus {
217 Blocked,
218 WaitingForExternal,
219 Enqueued,
220 Running,
221 Succeeded,
222 Failed,
223 Canceled,
224}
225
226impl WorkflowStepStatus {
227 #[must_use]
228 pub fn from_db_value(raw_status: &str) -> Option<Self> {
229 match raw_status {
230 "BLOCKED" => Some(Self::Blocked),
231 "WAITING_FOR_EXTERNAL" => Some(Self::WaitingForExternal),
232 "ENQUEUED" => Some(Self::Enqueued),
233 "RUNNING" => Some(Self::Running),
234 "SUCCEEDED" => Some(Self::Succeeded),
235 "FAILED" => Some(Self::Failed),
236 "CANCELED" => Some(Self::Canceled),
237 _ => None,
238 }
239 }
240
241 #[must_use]
242 pub const fn as_db_value(self) -> &'static str {
243 match self {
244 Self::Blocked => "BLOCKED",
245 Self::WaitingForExternal => "WAITING_FOR_EXTERNAL",
246 Self::Enqueued => "ENQUEUED",
247 Self::Running => "RUNNING",
248 Self::Succeeded => "SUCCEEDED",
249 Self::Failed => "FAILED",
250 Self::Canceled => "CANCELED",
251 }
252 }
253
254 #[must_use]
255 pub const fn is_terminal(self) -> bool {
256 matches!(self, Self::Succeeded | Self::Failed | Self::Canceled)
257 }
258}
259
260impl FromStr for WorkflowStepStatus {
261 type Err = ();
262
263 fn from_str(raw_status: &str) -> Result<Self, Self::Err> {
264 Self::from_db_value(raw_status).ok_or(())
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::{JobStage, WorkflowRunStatus, WorkflowStepStatus};
271 use proptest::prelude::*;
272
273 #[test]
274 fn parse_job_stage_from_str_rejects_invalid_value() {
275 assert!("NOT_A_REAL_STAGE".parse::<JobStage>().is_err());
276 }
277
278 #[test]
279 fn parse_workflow_run_status_from_str_rejects_invalid_value() {
280 assert!("NOT_A_REAL_STATUS".parse::<WorkflowRunStatus>().is_err());
281 }
282
283 #[test]
284 fn parse_workflow_step_status_from_str_rejects_invalid_value() {
285 assert!("NOT_A_REAL_STATUS".parse::<WorkflowStepStatus>().is_err());
286 }
287
288 proptest! {
289 #[test]
290 fn job_stage_roundtrips_db_value(
291 stage in prop_oneof![
292 Just(JobStage::Queued),
293 Just(JobStage::Scheduled),
294 Just(JobStage::Running),
295 Just(JobStage::Completed),
296 ]
297 ) {
298 let raw = stage.as_db_value();
299
300 prop_assert_eq!(JobStage::from_db_value(raw), Some(stage));
301 prop_assert_eq!(raw.parse::<JobStage>().ok(), Some(stage));
302 }
303
304 #[test]
305 fn workflow_step_status_roundtrips_db_value(
306 status in prop_oneof![
307 Just(WorkflowStepStatus::Blocked),
308 Just(WorkflowStepStatus::WaitingForExternal),
309 Just(WorkflowStepStatus::Enqueued),
310 Just(WorkflowStepStatus::Running),
311 Just(WorkflowStepStatus::Succeeded),
312 Just(WorkflowStepStatus::Failed),
313 Just(WorkflowStepStatus::Canceled),
314 ]
315 ) {
316 let raw = status.as_db_value();
317
318 prop_assert_eq!(WorkflowStepStatus::from_db_value(raw), Some(status));
319 prop_assert_eq!(raw.parse::<WorkflowStepStatus>().ok(), Some(status));
320 }
321
322 #[test]
323 fn workflow_run_status_roundtrips_db_value(
324 status in prop_oneof![
325 Just(WorkflowRunStatus::Running),
326 Just(WorkflowRunStatus::WaitingForExternal),
327 Just(WorkflowRunStatus::Succeeded),
328 Just(WorkflowRunStatus::CompletedWithErrors),
329 Just(WorkflowRunStatus::Canceled),
330 ]
331 ) {
332 let raw = status.as_db_value();
333
334 prop_assert_eq!(WorkflowRunStatus::from_db_value(raw), Some(status));
335 prop_assert_eq!(raw.parse::<WorkflowRunStatus>().ok(), Some(status));
336 }
337 }
338}