1use crate::ids::JobId;
5use serde::{Deserialize, Serialize};
6use time::OffsetDateTime;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
10#[serde(rename_all = "snake_case")]
11pub enum JobStatus {
12 Queued,
13 Running,
14 Succeeded,
15 Failed,
16 Cancelled,
17}
18
19impl JobStatus {
20 pub fn is_terminal(self) -> bool {
21 matches!(self, Self::Succeeded | Self::Failed | Self::Cancelled)
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
27pub struct Progress {
28 pub current: u64,
29 pub total: Option<u64>,
31}
32
33impl Progress {
34 pub fn indeterminate() -> Self {
35 Self {
36 current: 0,
37 total: None,
38 }
39 }
40
41 pub fn of(current: u64, total: u64) -> Self {
42 Self {
43 current,
44 total: Some(total),
45 }
46 }
47
48 pub fn ratio(&self) -> Option<f64> {
50 match self.total {
51 Some(total) if total > 0 => Some((self.current as f64 / total as f64).clamp(0.0, 1.0)),
52 _ => None,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
59pub struct Job {
60 pub id: JobId,
61 pub kind: String,
63 pub status: JobStatus,
64 pub progress: Progress,
65 pub cancelable: bool,
66 #[serde(with = "time::serde::rfc3339")]
67 #[cfg_attr(feature = "ts", ts(type = "string"))]
68 pub started_at: OffsetDateTime,
69 #[serde(with = "time::serde::rfc3339::option")]
70 #[cfg_attr(feature = "ts", ts(type = "string | null"))]
71 pub finished_at: Option<OffsetDateTime>,
72 pub error: Option<String>,
73}
74
75impl Job {
76 pub fn queued(kind: impl Into<String>, started_at: OffsetDateTime) -> Self {
77 Self {
78 id: JobId::generate(),
79 kind: kind.into(),
80 status: JobStatus::Queued,
81 progress: Progress::indeterminate(),
82 cancelable: true,
83 started_at,
84 finished_at: None,
85 error: None,
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn ratio_is_none_while_the_total_is_unknown() {
96 assert_eq!(Progress::indeterminate().ratio(), None);
97 }
98
99 #[test]
100 fn ratio_never_exceeds_one() {
101 assert_eq!(Progress::of(12, 10).ratio(), Some(1.0));
102 }
103}