1use std::fmt::Display;
5
6use chrono::{DateTime, Utc};
7use serde::de::DeserializeOwned;
8
9use crate::backend;
10
11pub mod builder;
12pub mod query;
13pub(crate) mod runner;
14pub mod uniqueness_criteria;
15
16#[derive(Debug, Eq, Clone, Copy)]
18pub struct JobId(i32);
19
20impl From<i32> for JobId {
21 fn from(value: i32) -> Self {
22 Self(value)
23 }
24}
25
26impl From<JobId> for i32 {
27 fn from(value: JobId) -> Self {
28 value.0
29 }
30}
31
32impl PartialEq<i32> for JobId {
33 fn eq(&self, other: &i32) -> bool {
34 self.0 == *other
35 }
36}
37
38impl PartialEq<JobId> for i32 {
39 fn eq(&self, other: &JobId) -> bool {
40 *self == other.0
41 }
42}
43
44impl PartialEq<JobId> for JobId {
45 fn eq(&self, other: &Self) -> bool {
46 self.0 == other.0
47 }
48}
49
50impl Display for JobId {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 write!(f, "JobId({})", self.0)
53 }
54}
55
56#[derive(Debug, Eq, PartialEq, Clone)]
62pub struct Job<D, M> {
64 pub id: JobId,
66 pub status: JobStatus,
68 pub executor: String,
72 pub data: D,
74 pub metadata: Option<M>,
76 pub attempt: u16,
78 pub max_attempts: u16,
80 pub priority: u16,
90 pub tags: Vec<String>,
92 pub errors: Vec<JobError>,
94 pub inserted_at: DateTime<Utc>,
96 pub scheduled_at: DateTime<Utc>,
98 pub attempted_at: Option<DateTime<Utc>>,
100 pub completed_at: Option<DateTime<Utc>>,
102 pub cancelled_at: Option<DateTime<Utc>>,
104 pub discarded_at: Option<DateTime<Utc>>,
106}
107
108impl<D, M> TryFrom<backend::Job> for Job<D, M>
109where
110 D: DeserializeOwned,
111 M: DeserializeOwned,
112{
113 type Error = serde_json::Error;
114
115 fn try_from(value: backend::Job) -> Result<Self, Self::Error> {
116 let data = serde_json::from_value(value.data)?;
117 let metadata = serde_json::from_value(value.metadata)?;
118 Ok(Self {
119 id: value.id.into(),
120 status: value.status,
121 executor: value.executor,
122 data,
123 metadata,
124 attempt: value.attempt.try_into().unwrap(),
125 attempted_at: value.attempted_at,
126 max_attempts: value.max_attempts.try_into().unwrap(),
127 priority: value.priority.try_into().unwrap(),
128 tags: value.tags,
129 errors: value.errors,
130 inserted_at: value.inserted_at,
131 scheduled_at: value.scheduled_at,
132 completed_at: value.completed_at,
133 cancelled_at: value.cancelled_at,
134 discarded_at: value.discarded_at,
135 })
136 }
137}
138
139impl<D, M> Job<D, M> {
140 pub(crate) fn is_final_attempt(&self) -> bool {
141 self.attempt == self.max_attempts
142 }
143}
144
145#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
149pub enum JobStatus {
150 Complete,
155 Executing,
157 Scheduled,
163 Retryable,
166 Cancelled,
169 Discarded,
171}
172
173impl JobStatus {
174 pub const ALL: [JobStatus; 6] = [
176 Self::Complete,
177 Self::Executing,
178 Self::Scheduled,
179 Self::Retryable,
180 Self::Cancelled,
181 Self::Discarded,
182 ];
183}
184
185#[derive(Debug, PartialEq, Eq, Clone)]
187pub struct JobError {
188 pub attempt: u16,
190 pub error_type: ErrorType,
192 pub details: String,
194 pub recorded_at: DateTime<Utc>,
196}
197
198#[derive(Debug, PartialEq, Eq, Clone)]
200pub enum ErrorType {
201 Panic,
203 Timeout,
207 Cancelled,
212 Other(String),
217}
218
219#[cfg(test)]
220mod test {
221 use super::*;
222
223 #[test]
224 fn job_id_display() {
225 let job_id = JobId(42);
226 assert_eq!(&job_id.to_string(), "JobId(42)");
227 }
228
229 #[test]
230 fn job_id_from_i32() {
231 let value = 42;
232 let job_id = JobId(42);
233 let into_job_id: JobId = value.into();
234 let into_i32: i32 = job_id.into();
235
236 assert_eq!(into_job_id, job_id);
237 assert_eq!(into_i32, value);
238 }
239
240 #[test]
241 fn from_backend_job_errors() {
242 let backend_job = backend::Job::raw_job();
243 let result = Job::<String, String>::try_from(backend_job);
244 assert!(result.is_err());
245
246 let backend_job =
247 backend::Job::raw_job().with_raw_metadata(serde_json::Value::Number(42.into()));
248 let result = Job::<(), String>::try_from(backend_job);
249 assert!(result.is_err());
250 }
251
252 #[test]
253 fn from_backend_job() {
254 let backend_job =
255 backend::Job::raw_job().with_raw_data(serde_json::Value::String("data".to_owned()));
256 let job = Job::<String, String>::try_from(backend_job.clone()).expect("Should not error");
257
258 assert_eq!(job.id, backend_job.id);
259 assert_eq!(job.status, backend_job.status);
260 assert_eq!(job.executor, backend_job.executor);
261 assert_eq!(&job.data, "data");
262 assert_eq!(job.metadata, None);
263 assert_eq!(job.attempt as i32, backend_job.attempt);
264 assert_eq!(job.max_attempts as i32, backend_job.max_attempts);
265 assert_eq!(job.priority as i32, backend_job.priority);
266 assert_eq!(job.tags, backend_job.tags);
267 assert_eq!(job.errors, backend_job.errors);
268 assert_eq!(job.inserted_at, backend_job.inserted_at);
269 assert_eq!(job.scheduled_at, backend_job.scheduled_at);
270 assert_eq!(job.attempted_at, backend_job.attempted_at);
271 assert_eq!(job.completed_at, backend_job.completed_at);
272 assert_eq!(job.cancelled_at, backend_job.cancelled_at);
273 assert_eq!(job.discarded_at, backend_job.discarded_at);
274 }
275}