1use chrono::{DateTime, Utc};
2#[cfg(feature = "valkey-store")]
3use serde::{Deserialize, Serialize};
4use std::collections::VecDeque;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum RunStatus {
8 Success,
9 Failed,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct RunRecord {
14 pub scheduled_at: DateTime<Utc>,
15 pub started_at: DateTime<Utc>,
16 pub finished_at: DateTime<Utc>,
17 pub catch_up: bool,
18 pub status: RunStatus,
19 pub error: Option<String>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23#[cfg_attr(feature = "valkey-store", derive(Serialize, Deserialize))]
24pub struct JobState {
25 pub job_id: String,
26 pub trigger_count: u32,
27 pub last_run_at: Option<DateTime<Utc>>,
28 pub last_success_at: Option<DateTime<Utc>>,
29 pub next_run_at: Option<DateTime<Utc>>,
30 pub last_error: Option<String>,
31}
32
33impl JobState {
34 pub fn new(job_id: impl Into<String>, next_run_at: Option<DateTime<Utc>>) -> Self {
35 Self {
36 job_id: job_id.into(),
37 trigger_count: 0,
38 last_run_at: None,
39 last_success_at: None,
40 next_run_at,
41 last_error: None,
42 }
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct SchedulerReport {
48 pub job_id: String,
49 pub state: JobState,
50 pub history: Vec<RunRecord>,
51}
52
53pub(crate) fn push_history(
54 history: &mut VecDeque<RunRecord>,
55 record: RunRecord,
56 history_limit: usize,
57) {
58 if history_limit == 0 {
59 return;
60 }
61
62 history.push_back(record);
63 while history.len() > history_limit {
64 history.pop_front();
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::{RunRecord, RunStatus, push_history};
71 use chrono::Utc;
72 use std::collections::VecDeque;
73
74 fn record(second: i64) -> RunRecord {
75 let instant = Utc::now() + chrono::TimeDelta::seconds(second);
76 RunRecord {
77 scheduled_at: instant,
78 started_at: instant,
79 finished_at: instant,
80 catch_up: false,
81 status: RunStatus::Success,
82 error: None,
83 }
84 }
85
86 #[test]
87 fn push_history_keeps_latest_records_within_limit() {
88 let base = Utc::now();
89 let mut history = VecDeque::new();
90 push_history(&mut history, record_at(base, 1), 2);
91 push_history(&mut history, record_at(base, 2), 2);
92 push_history(&mut history, record_at(base, 3), 2);
93
94 assert_eq!(history.len(), 2);
95 assert_eq!(
96 history.front().unwrap().scheduled_at,
97 record_at(base, 2).scheduled_at
98 );
99 assert_eq!(
100 history.back().unwrap().scheduled_at,
101 record_at(base, 3).scheduled_at
102 );
103 }
104
105 #[test]
106 fn push_history_ignores_records_when_limit_is_zero() {
107 let mut history = VecDeque::new();
108 push_history(&mut history, record(1), 0);
109
110 assert!(history.is_empty());
111 }
112
113 fn record_at(base: chrono::DateTime<Utc>, second: i64) -> RunRecord {
114 let instant = base + chrono::TimeDelta::seconds(second);
115 RunRecord {
116 scheduled_at: instant,
117 started_at: instant,
118 finished_at: instant,
119 catch_up: false,
120 status: RunStatus::Success,
121 error: None,
122 }
123 }
124}