Skip to main content

scheduler/model/
state.rs

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}