1use serde::{de::DeserializeOwned, Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum QueuePriority {
9 Low,
10 Normal,
11 High,
12}
13
14impl QueuePriority {
15 pub fn as_i32(&self) -> i32 {
16 match self {
17 QueuePriority::Low => 3,
18 QueuePriority::Normal => 2,
19 QueuePriority::High => 1,
20 }
21 }
22
23 pub fn from_i32(val: i32) -> Self {
24 match val {
25 1 => QueuePriority::High,
26 2 => QueuePriority::Normal,
27 _ => QueuePriority::Low,
28 }
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub enum QueueJobStatus {
35 Pending,
36 Processing,
37 Completed,
38 Failed,
39 Cancelled,
40}
41
42impl QueueJobStatus {
43 pub fn as_str(&self) -> &str {
44 match self {
45 QueueJobStatus::Pending => "pending",
46 QueueJobStatus::Processing => "processing",
47 QueueJobStatus::Completed => "completed",
48 QueueJobStatus::Failed => "failed",
49 QueueJobStatus::Cancelled => "cancelled",
50 }
51 }
52
53 pub fn parse(s: &str) -> Option<Self> {
54 match s {
55 "pending" => Some(QueueJobStatus::Pending),
56 "processing" => Some(QueueJobStatus::Processing),
57 "completed" => Some(QueueJobStatus::Completed),
58 "failed" => Some(QueueJobStatus::Failed),
59 "cancelled" => Some(QueueJobStatus::Cancelled),
60 _ => None,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(bound(deserialize = "T: DeserializeOwned"))]
87pub struct QueueJob<T>
88where
89 T: Serialize + DeserializeOwned + Clone + Send + Sync,
90{
91 pub id: String,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub trace_id: Option<String>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
106 pub trace_ctx: Option<stack_ids::TraceCtx>,
107 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub attempt_id: Option<stack_ids::AttemptId>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub trial_id: Option<stack_ids::TrialId>,
119 pub priority: QueuePriority,
120 pub status: QueueJobStatus,
121 pub data: T,
122 pub created_at: Option<String>,
124 pub started_at: Option<String>,
126 pub completed_at: Option<String>,
128 pub error_message: Option<String>,
130}
131
132impl<T> QueueJob<T>
133where
134 T: Serialize + DeserializeOwned + Clone + Send + Sync,
135{
136 pub fn new(data: T) -> Self {
141 Self {
142 id: uuid::Uuid::new_v4().to_string(),
143 trace_id: None,
144 trace_ctx: None,
145 attempt_id: Some(stack_ids::AttemptId::generate()),
146 trial_id: None,
147 priority: QueuePriority::Normal,
148 status: QueueJobStatus::Pending,
149 data,
150 created_at: None,
151 started_at: None,
152 completed_at: None,
153 error_message: None,
154 }
155 }
156
157 pub fn with_priority(mut self, priority: QueuePriority) -> Self {
159 self.priority = priority;
160 self
161 }
162
163 pub fn with_id(mut self, id: String) -> Self {
165 self.id = id;
166 self
167 }
168
169 pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
175 let id = trace_id.into();
176 self.trace_ctx = Some(stack_ids::TraceCtx::from_legacy_trace_id(&id));
178 self.trace_id = Some(id);
179 self
180 }
181
182 pub fn with_trace_ctx(mut self, ctx: stack_ids::TraceCtx) -> Self {
187 self.trace_id = Some(ctx.trace_id.clone());
188 self.trace_ctx = Some(ctx);
189 self
190 }
191
192 pub fn with_attempt_id(mut self, attempt_id: stack_ids::AttemptId) -> Self {
197 self.attempt_id = Some(attempt_id);
198 self
199 }
200
201 pub fn with_trial_id(mut self, trial_id: stack_ids::TrialId) -> Self {
203 self.trial_id = Some(trial_id);
204 self
205 }
206
207 pub fn resolve_trace_ctx(&self) -> Option<stack_ids::TraceCtx> {
212 self.trace_ctx.clone().or_else(|| {
213 self.trace_id
214 .as_ref()
215 .map(stack_ids::TraceCtx::from_legacy_trace_id)
216 })
217 }
218
219 pub fn trace_ctx_compat(&self) -> Option<stack_ids::TraceCtx> {
225 self.resolve_trace_ctx()
226 }
227}
228
229#[derive(Debug, Clone, Default, Serialize, Deserialize)]
231pub struct QueueStats {
232 pub pending: u32,
233 pub processing: u32,
234 pub completed: u32,
235 pub failed: u32,
236 pub cancelled: u32,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
241pub enum FailureClass {
242 Transient,
244 Permanent,
246 RateLimited { retry_after_secs: u64 },
248}
249
250impl FailureClass {
251 pub fn as_str(&self) -> &str {
252 match self {
253 Self::Transient => "transient",
254 Self::Permanent => "permanent",
255 Self::RateLimited { .. } => "rate_limited",
256 }
257 }
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct JobResult {
263 pub success: bool,
264 pub output: Option<String>,
265 pub error: Option<String>,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
267 pub failure_class: Option<FailureClass>,
268}
269
270impl JobResult {
271 pub fn success() -> Self {
273 Self {
274 success: true,
275 output: None,
276 error: None,
277 failure_class: None,
278 }
279 }
280
281 pub fn success_with_output(output: String) -> Self {
283 Self {
284 success: true,
285 output: Some(output),
286 error: None,
287 failure_class: None,
288 }
289 }
290
291 pub fn failure(error: String) -> Self {
293 Self {
294 success: false,
295 output: None,
296 error: Some(error),
297 failure_class: Some(FailureClass::Permanent),
298 }
299 }
300
301 pub fn with_failure_class(mut self, failure_class: FailureClass) -> Self {
303 self.failure_class = Some(failure_class);
304 self
305 }
306
307 pub fn transient_failure(error: String) -> Self {
309 Self::failure(error).with_failure_class(FailureClass::Transient)
310 }
311
312 pub fn rate_limited(error: String, retry_after_secs: u64) -> Self {
314 Self::failure(error).with_failure_class(FailureClass::RateLimited { retry_after_secs })
315 }
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
320#[serde(rename_all = "camelCase")]
321pub struct QueueJobDetails {
322 pub id: String,
323 pub priority: QueuePriority,
324 pub status: QueueJobStatus,
325 pub data_json: String,
326 pub trace_id: Option<String>,
327 pub created_at: Option<String>,
328 pub started_at: Option<String>,
329 pub completed_at: Option<String>,
330 pub error_message: Option<String>,
331 pub worker_id: Option<String>,
332 pub heartbeat_at: Option<String>,
333 pub visibility_timeout_secs: u64,
334 pub failure_class: Option<String>,
335 pub next_run_at: Option<String>,
336 pub attempt_count: u32,
337 pub attempt_id: Option<String>,
339 pub trial_id: Option<String>,
341}