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 #[must_use]
206 pub const fn is_terminal(self) -> bool {
207 matches!(
208 self,
209 Self::Succeeded | Self::CompletedWithErrors | Self::Canceled
210 )
211 }
212}
213
214impl FromStr for WorkflowRunStatus {
215 type Err = ();
216
217 fn from_str(raw_status: &str) -> Result<Self, Self::Err> {
218 Self::from_db_value(raw_status).ok_or(())
219 }
220}
221
222#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
223#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
224pub enum WorkflowStepStatus {
225 Blocked,
226 WaitingForExternal,
227 Enqueued,
228 Running,
229 Succeeded,
230 Failed,
231 Canceled,
232}
233
234impl WorkflowStepStatus {
235 #[must_use]
236 pub fn from_db_value(raw_status: &str) -> Option<Self> {
237 match raw_status {
238 "BLOCKED" => Some(Self::Blocked),
239 "WAITING_FOR_EXTERNAL" => Some(Self::WaitingForExternal),
240 "ENQUEUED" => Some(Self::Enqueued),
241 "RUNNING" => Some(Self::Running),
242 "SUCCEEDED" => Some(Self::Succeeded),
243 "FAILED" => Some(Self::Failed),
244 "CANCELED" => Some(Self::Canceled),
245 _ => None,
246 }
247 }
248
249 #[must_use]
250 pub const fn as_db_value(self) -> &'static str {
251 match self {
252 Self::Blocked => "BLOCKED",
253 Self::WaitingForExternal => "WAITING_FOR_EXTERNAL",
254 Self::Enqueued => "ENQUEUED",
255 Self::Running => "RUNNING",
256 Self::Succeeded => "SUCCEEDED",
257 Self::Failed => "FAILED",
258 Self::Canceled => "CANCELED",
259 }
260 }
261
262 #[must_use]
263 pub const fn is_terminal(self) -> bool {
264 matches!(self, Self::Succeeded | Self::Failed | Self::Canceled)
265 }
266}
267
268impl FromStr for WorkflowStepStatus {
269 type Err = ();
270
271 fn from_str(raw_status: &str) -> Result<Self, Self::Err> {
272 Self::from_db_value(raw_status).ok_or(())
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::{JobStage, WorkflowRunStatus, WorkflowStepStatus};
279 use proptest::prelude::*;
280
281 #[test]
282 fn parse_job_stage_from_str_rejects_invalid_value() {
283 assert!("NOT_A_REAL_STAGE".parse::<JobStage>().is_err());
284 }
285
286 #[test]
287 fn parse_workflow_run_status_from_str_rejects_invalid_value() {
288 assert!("NOT_A_REAL_STATUS".parse::<WorkflowRunStatus>().is_err());
289 }
290
291 #[test]
292 fn parse_workflow_step_status_from_str_rejects_invalid_value() {
293 assert!("NOT_A_REAL_STATUS".parse::<WorkflowStepStatus>().is_err());
294 }
295
296 proptest! {
297 #[test]
298 fn job_stage_roundtrips_db_value(
299 stage in prop_oneof![
300 Just(JobStage::Queued),
301 Just(JobStage::Scheduled),
302 Just(JobStage::Running),
303 Just(JobStage::Completed),
304 ]
305 ) {
306 let raw = stage.as_db_value();
307
308 prop_assert_eq!(JobStage::from_db_value(raw), Some(stage));
309 prop_assert_eq!(raw.parse::<JobStage>().ok(), Some(stage));
310 }
311
312 #[test]
313 fn workflow_step_status_roundtrips_db_value(
314 status in prop_oneof![
315 Just(WorkflowStepStatus::Blocked),
316 Just(WorkflowStepStatus::WaitingForExternal),
317 Just(WorkflowStepStatus::Enqueued),
318 Just(WorkflowStepStatus::Running),
319 Just(WorkflowStepStatus::Succeeded),
320 Just(WorkflowStepStatus::Failed),
321 Just(WorkflowStepStatus::Canceled),
322 ]
323 ) {
324 let raw = status.as_db_value();
325
326 prop_assert_eq!(WorkflowStepStatus::from_db_value(raw), Some(status));
327 prop_assert_eq!(raw.parse::<WorkflowStepStatus>().ok(), Some(status));
328 }
329
330 #[test]
331 fn workflow_run_status_roundtrips_db_value(
332 status in prop_oneof![
333 Just(WorkflowRunStatus::Running),
334 Just(WorkflowRunStatus::WaitingForExternal),
335 Just(WorkflowRunStatus::Succeeded),
336 Just(WorkflowRunStatus::CompletedWithErrors),
337 Just(WorkflowRunStatus::Canceled),
338 ]
339 ) {
340 let raw = status.as_db_value();
341
342 prop_assert_eq!(WorkflowRunStatus::from_db_value(raw), Some(status));
343 prop_assert_eq!(raw.parse::<WorkflowRunStatus>().ok(), Some(status));
344 }
345 }
346}