1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Serialize, Deserialize, Clone)]
5pub struct JobErrorReturn {
6 #[serde(rename = "error")]
7 pub error_string: String,
8 pub retry_worthy: bool,
9}
10
11#[derive(Debug, Serialize, Deserialize, Clone)]
12pub struct ProgressMessage {
13 #[serde(rename = "task_id")]
14 pub task_id: String,
15
16 #[serde(rename = "data")]
17 pub data: String,
18}
19
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub struct RetryableTask {
22 #[serde(rename = "taskId")]
23 pub task_id: String,
24
25 #[serde(rename = "retryCount")]
26 pub retry_count: i32,
27
28 #[serde(rename = "maxRetries")]
29 pub max_retries: i32,
30
31 #[serde(rename = "retryAfterHours")]
32 pub retry_after_hours: f64,
33
34 #[serde(rename = "retryAfterTime")]
35 pub retry_after_time: DateTime<Utc>,
36
37 #[serde(rename = "taskData")]
38 pub task_data: String,
39
40 #[serde(rename = "taskType")]
41 pub task_type: String,
42
43 #[serde(rename = "LastErrorObj", skip_serializing_if = "Option::is_none")]
44 pub last_error_obj: Option<String>,
45
46 #[serde(rename = "LastJobError", skip_serializing_if = "Option::is_none")]
47 pub last_job_error: Option<JobErrorReturn>,
48
49 #[serde(rename = "rateLimitGroup", skip_serializing_if = "Option::is_none")]
50 pub rate_limit_group: Option<String>,
51
52 #[serde(rename = "maxPerMinute", skip_serializing_if = "Option::is_none")]
53 pub max_per_minute: Option<i32>,
54
55 #[serde(rename = "autoDedupe", skip_serializing_if = "Option::is_none")]
56 pub auto_dedupe: Option<bool>,
57
58 #[serde(rename = "urgencyScore", skip_serializing_if = "Option::is_none")]
59 pub urgency_score: Option<f64>,
60
61 #[serde(rename = "payloadHash", skip_serializing_if = "Option::is_none")]
62 pub payload_hash: Option<String>,
63
64 #[serde(rename = "deletedAt", skip_serializing_if = "Option::is_none")]
65 pub deleted_at: Option<DateTime<Utc>>,
66
67 #[serde(skip, default = "Utc::now")]
68 pub created_at: DateTime<Utc>,
69
70 #[serde(skip, default = "Utc::now")]
71 pub updated_at: DateTime<Utc>,
72}
73
74impl RetryableTask {
75 pub fn new(
76 task_id: String,
77 task_type: String,
78 task_data: String,
79 max_retries: i32,
80 retry_after_hours: f64,
81 rate_limit_group: Option<String>,
82 max_per_minute: Option<i32>,
83 auto_dedupe: Option<bool>,
84 urgency_score: Option<f64>,
85 ) -> Self {
86 let now = Utc::now();
87 let payload_hash = if auto_dedupe.unwrap_or(false) {
88 use xxhash_rust::xxh64::xxh64;
89 let combined = format!("{}{}", task_type, task_data);
90 Some(format!("{:x}", xxh64(combined.as_bytes(), 0)))
91 } else {
92 None
93 };
94
95 Self {
96 task_id,
97 task_type,
98 task_data,
99 max_retries,
100 retry_after_hours,
101 retry_count: 0,
102 retry_after_time: now,
103 last_error_obj: None,
104 last_job_error: None,
105 rate_limit_group,
106 max_per_minute,
107 auto_dedupe,
108 urgency_score,
109 payload_hash,
110 deleted_at: None,
111 created_at: now,
112 updated_at: now,
113 }
114 }
115
116 pub fn mark_deleted(&mut self) {
117 self.deleted_at = Some(Utc::now());
118 self.updated_at = Utc::now();
119 }
120
121 pub fn update_retry_config(&mut self, error_msg: Option<String>) {
122 self.retry_count += 1;
123
124 let seconds = (self.retry_after_hours * 3600.0) as i64;
126 self.retry_after_time = Utc::now() + chrono::Duration::seconds(seconds);
127
128 self.last_error_obj = error_msg.clone();
129
130 if let Some(msg) = error_msg {
131 self.last_job_error = Some(JobErrorReturn {
132 error_string: msg,
133 retry_worthy: true,
134 });
135 } else {
136 self.last_job_error = None;
137 }
138
139 self.updated_at = Utc::now();
140 }
141}
142
143use std::cmp::Ordering;
144
145#[derive(Clone)]
146pub struct PriorityTask(pub RetryableTask);
147
148impl PartialEq for PriorityTask {
149 fn eq(&self, other: &Self) -> bool {
150 self.0.task_id == other.0.task_id
151 }
152}
153
154impl Eq for PriorityTask {}
155
156impl PartialOrd for PriorityTask {
157 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
158 Some(self.cmp(other))
159 }
160}
161
162impl Ord for PriorityTask {
163 fn cmp(&self, other: &Self) -> Ordering {
164 let score_a = self.0.urgency_score.unwrap_or(0.0);
165 let score_b = other.0.urgency_score.unwrap_or(0.0);
166
167 score_a.partial_cmp(&score_b).unwrap_or(Ordering::Equal)
169 }
170}