1use serde::{Deserialize, Serialize};
2use std::{str::FromStr, time::SystemTime};
3use uuid::Uuid;
4
5#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
6pub enum TaskStatus {
7 Queued,
8 InProgress,
9 Completed,
10 Failed,
11 DeadLetter,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct TaskId(Uuid);
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
23pub struct Task<D: Clone> {
24 pub task_id: TaskId,
25 pub payload: D,
26 pub status: TaskStatus,
27 pub queued_at: SystemTime,
28 pub started_at: Option<SystemTime>,
29 pub finished_at: Option<SystemTime>,
30 pub retries: u32,
31 pub error_msg: Option<String>,
32}
33
34impl<D: Clone> Task<D> {
35 pub fn new(payload: D) -> Self {
36 Task {
37 task_id: TaskId::new(),
38 payload,
39 status: TaskStatus::Queued,
40 queued_at: SystemTime::now(),
41 started_at: None,
42 finished_at: None,
43 retries: 0,
44 error_msg: None,
45 }
46 }
47
48 pub fn set_in_progress(&mut self) {
49 self.status = TaskStatus::InProgress;
50 self.started_at = Some(SystemTime::now());
51 }
52
53 pub fn set_succeed(&mut self) {
54 self.status = TaskStatus::Completed;
55 self.finished_at = Some(SystemTime::now());
56 }
57
58 pub fn set_retry(&mut self, err_msg: &str) {
59 self.status = TaskStatus::Failed;
60 self.finished_at = Some(SystemTime::now());
61 self.retries += 1;
62 self.error_msg = Some(err_msg.to_string());
63 }
64
65 pub fn set_dlq(&mut self, err_msg: &str) {
66 self.status = TaskStatus::DeadLetter;
67 self.finished_at = Some(SystemTime::now());
68 self.error_msg = Some(err_msg.to_string());
69 }
70
71 pub fn set_status(&mut self, new_status: TaskStatus) {
72 self.status = new_status;
73 }
74
75 pub fn get_payload(&self) -> &D {
76 &self.payload
77 }
78}
79
80impl TaskId {
81 pub fn new() -> Self {
82 Self(Uuid::now_v7())
83 }
84
85 pub fn get(&self) -> Uuid {
86 self.0
87 }
88
89 pub fn as_bytes(&self) -> &[u8; 16] {
90 self.0.as_bytes()
91 }
92
93 pub fn from_uuid(uuid: Uuid) -> Self {
94 Self(uuid)
95 }
96
97 pub fn from_string(uuid_str: &str) -> Result<Self, uuid::Error> {
98 let uuid = Uuid::from_str(uuid_str)?;
99 Ok(Self(uuid))
100 }
101}
102
103impl FromStr for TaskId {
104 type Err = uuid::Error;
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 Self::from_string(s)
107 }
108}
109
110impl Default for TaskId {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116impl serde::Serialize for TaskId {
118 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
119 where
120 S: serde::Serializer,
121 {
122 self.0.serialize(serializer)
124 }
125}
126
127impl<'de> serde::Deserialize<'de> for TaskId {
129 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
130 where
131 D: serde::Deserializer<'de>,
132 {
133 let uuid = Uuid::deserialize(deserializer)?;
135 Ok(TaskId(uuid))
136 }
137}
138
139impl std::fmt::Display for TaskId {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 write!(f, "TaskId({})", self.0)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use core::panic;
148
149 use super::*;
150 use serde::{Deserialize, Serialize};
151
152 #[derive(Clone, Serialize, Deserialize, Default)]
153 struct TaskData {
154 value: u32,
155 }
156
157 #[test]
158 fn task_id_from_string_roundtrip() {
159 let original = TaskId::new();
160 let parsed = TaskId::from_string(&original.get().to_string()).unwrap();
161 assert_eq!(original, parsed);
162 let parsed_via_fromstr: TaskId =
163 original.get().to_string().parse().unwrap();
164 assert_eq!(original, parsed_via_fromstr);
165 }
166
167 #[test]
168 fn task_id_from_string_rejects_invalid() {
169 assert!(TaskId::from_string("not-a-uuid").is_err());
170 assert!("not-a-uuid".parse::<TaskId>().is_err());
171 }
172
173 #[test]
174 fn task_uuid_conversion() {
175 let task = Task::new(TaskData { value: 42 });
176 let uuid_string = task.task_id.get().to_string();
177 assert!(!uuid_string.contains("TaskId"));
178 }
179
180 #[test]
181 fn task_id_serde() {
182 let task = Task::new(TaskData { value: 1 });
183 let task_id = task.task_id;
184 let serialized_task_value = serde_json::to_value(task).unwrap();
185 let serialized_task_json = serialized_task_value.to_string();
186 let desrialized_task: Task<TaskData> =
187 serde_json::from_str(&serialized_task_json).unwrap();
188 assert_eq!(task_id, desrialized_task.task_id);
189 }
190
191 #[test]
192 fn test_task_creation() {
193 let task = Task::new(TaskData::default());
194 assert!(task.started_at.is_none());
195 assert!(task.finished_at.is_none());
196 assert_eq!(task.retries, 0);
197 assert_eq!(task.payload.value, 0);
198 match task.status {
199 TaskStatus::Queued => {}
200 _ => panic!("Wrong status (task.status)"),
201 };
202 }
203
204 #[test]
205 fn test_in_progress() {
206 let mut task = Task::new(TaskData::default());
207
208 task.set_in_progress();
209 match task.status {
210 TaskStatus::InProgress => {}
211 _ => panic!("Wrong status (task.status)"),
212 };
213 assert!(task.started_at.is_some());
214 assert!(task.finished_at.is_none());
215 }
216
217 #[test]
218 fn test_succeed() {
219 let mut task = Task::new(TaskData::default());
220
221 task.set_succeed();
222 match task.status {
223 TaskStatus::Completed => {}
224 _ => panic!("Wrong status (task.status)"),
225 };
226 assert!(task.finished_at.is_some());
227 assert!(task.started_at.is_none());
228 }
229
230 #[test]
231 fn test_set_retry() {
232 let mut task = Task::new(TaskData::default());
233
234 task.set_retry("Wrong task value");
235 match task.status {
236 TaskStatus::Failed => {}
237 _ => panic!("Wrong status (task.status)"),
238 };
239 assert!(task.finished_at.is_some());
240 assert_eq!(task.retries, 1);
241 assert!(task.error_msg.is_some());
242 assert!(task.started_at.is_none());
243 }
244
245 #[test]
246 fn test_set_dlq() {
247 let mut task = Task::new(TaskData::default());
248
249 task.set_dlq("Wrong task value");
250 match task.status {
251 TaskStatus::DeadLetter => {}
252 _ => panic!("Wrong status (task.status)"),
253 };
254 assert!(task.finished_at.is_some());
255 assert!(task.started_at.is_none());
256 assert!(task.error_msg.is_some());
257 }
258
259 #[test]
260 fn task_flow_succeed() {
261 let mut task = Task::new(TaskData::default());
262
263 task.set_in_progress();
264 task.payload.value += 1;
265
266 std::thread::sleep(std::time::Duration::from_millis(5));
267
268 task.set_retry("Wrong task value");
269 task.payload.value += 1;
270
271 task.set_in_progress();
272 std::thread::sleep(std::time::Duration::from_millis(5));
273
274 task.set_succeed();
275
276 match task.status {
277 TaskStatus::Completed => {}
278 _ => panic!("Wrong status (task.status)"),
279 };
280 assert_eq!(task.retries, 1);
281 assert_eq!(task.get_payload().value, 2);
282 assert!(task.started_at.is_some());
283 assert!(task.finished_at.is_some());
284
285 assert!(
287 task.finished_at
288 .unwrap()
289 .duration_since(task.started_at.unwrap())
290 .unwrap()
291 < std::time::Duration::from_millis(50)
292 );
293 assert!(
295 task.finished_at
296 .unwrap()
297 .duration_since(task.queued_at)
298 .unwrap()
299 >= std::time::Duration::from_millis(10)
300 );
301 }
302
303 #[test]
304 fn test_flow() {
305 let mut task = Task::new(TaskData::default());
306
307 task.set_in_progress();
308 task.payload.value += 1;
309
310 std::thread::sleep(std::time::Duration::from_millis(5));
311
312 task.set_retry("Wrong task value");
313 task.payload.value += 1;
314
315 task.set_in_progress();
316 std::thread::sleep(std::time::Duration::from_millis(5));
317
318 task.set_dlq("Failed to complete task");
319
320 match task.status {
321 TaskStatus::DeadLetter => {}
322 _ => panic!("Wrong status (task.status)"),
323 };
324 assert_eq!(task.retries, 1);
325 assert_eq!(task.get_payload().value, 2);
326 assert!(task.started_at.is_some());
327 assert!(task.finished_at.is_some());
328
329 assert!(
331 task.finished_at
332 .unwrap()
333 .duration_since(task.started_at.unwrap())
334 .unwrap()
335 < std::time::Duration::from_millis(50)
336 );
337 assert!(
339 task.finished_at
340 .unwrap()
341 .duration_since(task.queued_at)
342 .unwrap()
343 >= std::time::Duration::from_millis(10)
344 );
345 }
346}