Skip to main content

runledger_core/jobs/
status.rs

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}