modo/job/meta.rs
1use std::fmt;
2
3/// Job lifecycle status.
4///
5/// | Variant | Meaning |
6/// |---|---|
7/// | `Pending` | Waiting to be picked up by a worker |
8/// | `Running` | Currently being executed |
9/// | `Completed` | Finished successfully |
10/// | `Dead` | Exhausted all retry attempts |
11/// | `Cancelled` | Cancelled before execution via [`Enqueuer::cancel`](super::enqueuer::Enqueuer::cancel) |
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Status {
14 /// Waiting to be picked up by a worker.
15 Pending,
16 /// Currently being executed.
17 Running,
18 /// Finished successfully.
19 Completed,
20 /// Exhausted all retry attempts; will not be retried.
21 Dead,
22 /// Cancelled before execution.
23 Cancelled,
24}
25
26impl Status {
27 /// Returns the lowercase string representation of this status.
28 pub fn as_str(&self) -> &'static str {
29 match self {
30 Self::Pending => "pending",
31 Self::Running => "running",
32 Self::Completed => "completed",
33 Self::Dead => "dead",
34 Self::Cancelled => "cancelled",
35 }
36 }
37
38 /// Parses a status from its lowercase string representation.
39 ///
40 /// Returns `None` for unknown strings.
41 #[allow(clippy::should_implement_trait)]
42 pub fn from_str(s: &str) -> Option<Self> {
43 match s {
44 "pending" => Some(Self::Pending),
45 "running" => Some(Self::Running),
46 "completed" => Some(Self::Completed),
47 "dead" => Some(Self::Dead),
48 "cancelled" => Some(Self::Cancelled),
49 _ => None,
50 }
51 }
52
53 /// Returns `true` for statuses that are final: `Completed`, `Dead`, or
54 /// `Cancelled`.
55 pub fn is_terminal(&self) -> bool {
56 matches!(self, Self::Completed | Self::Dead | Self::Cancelled)
57 }
58}
59
60impl fmt::Display for Status {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.write_str(self.as_str())
63 }
64}
65
66/// Metadata about the currently executing job, available as a handler argument.
67///
68/// Extract `Meta` in a handler function to inspect the job's identity and
69/// retry state at runtime.
70#[derive(Debug, Clone)]
71pub struct Meta {
72 /// Unique job ID (ULID format).
73 pub id: String,
74 /// Registered handler name used to identify the job type.
75 pub name: String,
76 /// Name of the queue this job belongs to.
77 pub queue: String,
78 /// Current attempt number (1-based; incremented on each execution).
79 pub attempt: u32,
80 /// Maximum number of attempts before the job is marked `Dead`.
81 pub max_attempts: u32,
82 /// Absolute deadline for this execution; `None` if no timeout is set.
83 pub deadline: Option<tokio::time::Instant>,
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn status_roundtrip() {
92 let statuses = [
93 Status::Pending,
94 Status::Running,
95 Status::Completed,
96 Status::Dead,
97 Status::Cancelled,
98 ];
99 for s in &statuses {
100 let parsed = Status::from_str(s.as_str()).unwrap();
101 assert_eq!(&parsed, s);
102 }
103 }
104
105 #[test]
106 fn status_unknown_returns_none() {
107 assert!(Status::from_str("unknown").is_none());
108 }
109
110 #[test]
111 fn terminal_states() {
112 assert!(!Status::Pending.is_terminal());
113 assert!(!Status::Running.is_terminal());
114 assert!(Status::Completed.is_terminal());
115 assert!(Status::Dead.is_terminal());
116 assert!(Status::Cancelled.is_terminal());
117 }
118}