Skip to main content

kbolt_types/
schedule.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4pub struct AddScheduleRequest {
5    pub trigger: ScheduleTrigger,
6    pub scope: ScheduleScope,
7}
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub struct ScheduleAddResponse {
11    pub schedule: ScheduleDefinition,
12    pub backend: ScheduleBackend,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16pub struct ScheduleDefinition {
17    pub id: String,
18    pub trigger: ScheduleTrigger,
19    pub scope: ScheduleScope,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23#[serde(tag = "kind", rename_all = "snake_case")]
24pub enum ScheduleTrigger {
25    Every {
26        interval: ScheduleInterval,
27    },
28    Daily {
29        time: String,
30    },
31    Weekly {
32        weekdays: Vec<ScheduleWeekday>,
33        time: String,
34    },
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
38pub struct ScheduleInterval {
39    pub value: u32,
40    pub unit: ScheduleIntervalUnit,
41}
42
43#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
44#[serde(rename_all = "snake_case")]
45pub enum ScheduleIntervalUnit {
46    Minutes,
47    Hours,
48}
49
50#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
51#[serde(rename_all = "snake_case")]
52pub enum ScheduleWeekday {
53    Mon,
54    Tue,
55    Wed,
56    Thu,
57    Fri,
58    Sat,
59    Sun,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(tag = "kind", rename_all = "snake_case")]
64pub enum ScheduleScope {
65    All,
66    Space {
67        space: String,
68    },
69    Collections {
70        space: String,
71        collections: Vec<String>,
72    },
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
76pub struct ScheduleStatusResponse {
77    pub schedules: Vec<ScheduleStatusEntry>,
78    pub orphans: Vec<ScheduleOrphan>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
82pub struct ScheduleStatusEntry {
83    pub schedule: ScheduleDefinition,
84    pub backend: ScheduleBackend,
85    pub state: ScheduleState,
86    pub run_state: ScheduleRunState,
87}
88
89#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
90#[serde(rename_all = "snake_case")]
91pub enum ScheduleBackend {
92    Launchd,
93    SystemdUser,
94}
95
96#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
97#[serde(rename_all = "snake_case")]
98pub enum ScheduleState {
99    Installed,
100    Drifted,
101    TargetMissing,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
105pub struct ScheduleRunState {
106    pub last_started: Option<String>,
107    pub last_finished: Option<String>,
108    pub last_result: Option<ScheduleRunResult>,
109    pub last_error: Option<String>,
110}
111
112#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
113#[serde(rename_all = "snake_case")]
114pub enum ScheduleRunResult {
115    Success,
116    SkippedLock,
117    Failed,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
121pub struct ScheduleOrphan {
122    pub id: String,
123    pub backend: ScheduleBackend,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
127pub enum RemoveScheduleSelector {
128    Id { id: String },
129    All,
130    Scope { scope: ScheduleScope },
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
134pub struct RemoveScheduleRequest {
135    pub selector: RemoveScheduleSelector,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
139pub struct ScheduleRemoveResponse {
140    pub removed_ids: Vec<String>,
141}
142
143#[cfg(test)]
144mod tests {
145    use serde_json::json;
146
147    use super::{
148        ScheduleBackend, ScheduleDefinition, ScheduleInterval, ScheduleIntervalUnit,
149        ScheduleOrphan, ScheduleRunResult, ScheduleRunState, ScheduleScope, ScheduleState,
150        ScheduleStatusEntry, ScheduleStatusResponse, ScheduleTrigger, ScheduleWeekday,
151    };
152
153    #[test]
154    fn schedule_definition_serializes_weekly_collection_scope_as_snake_case() {
155        let value = serde_json::to_value(ScheduleDefinition {
156            id: "s2".to_string(),
157            trigger: ScheduleTrigger::Weekly {
158                weekdays: vec![ScheduleWeekday::Mon, ScheduleWeekday::Fri],
159                time: "15:00".to_string(),
160            },
161            scope: ScheduleScope::Collections {
162                space: "work".to_string(),
163                collections: vec!["api".to_string(), "docs".to_string()],
164            },
165        })
166        .expect("serialize schedule definition");
167
168        assert_eq!(
169            value,
170            json!({
171                "id": "s2",
172                "trigger": {
173                    "kind": "weekly",
174                    "weekdays": ["mon", "fri"],
175                    "time": "15:00"
176                },
177                "scope": {
178                    "kind": "collections",
179                    "space": "work",
180                    "collections": ["api", "docs"]
181                }
182            })
183        );
184    }
185
186    #[test]
187    fn schedule_status_response_serializes_run_state_and_orphans() {
188        let value = serde_json::to_value(ScheduleStatusResponse {
189            schedules: vec![ScheduleStatusEntry {
190                schedule: ScheduleDefinition {
191                    id: "s1".to_string(),
192                    trigger: ScheduleTrigger::Every {
193                        interval: ScheduleInterval {
194                            value: 30,
195                            unit: ScheduleIntervalUnit::Minutes,
196                        },
197                    },
198                    scope: ScheduleScope::All,
199                },
200                backend: ScheduleBackend::Launchd,
201                state: ScheduleState::Installed,
202                run_state: ScheduleRunState {
203                    last_started: Some("2026-03-07T15:00:00Z".to_string()),
204                    last_finished: Some("2026-03-07T15:00:09Z".to_string()),
205                    last_result: Some(ScheduleRunResult::Success),
206                    last_error: None,
207                },
208            }],
209            orphans: vec![ScheduleOrphan {
210                id: "s9".to_string(),
211                backend: ScheduleBackend::SystemdUser,
212            }],
213        })
214        .expect("serialize schedule status response");
215
216        assert_eq!(
217            value,
218            json!({
219                "schedules": [
220                    {
221                        "schedule": {
222                            "id": "s1",
223                            "trigger": {
224                                "kind": "every",
225                                "interval": {
226                                    "value": 30,
227                                    "unit": "minutes"
228                                }
229                            },
230                            "scope": {
231                                "kind": "all"
232                            }
233                        },
234                        "backend": "launchd",
235                        "state": "installed",
236                        "run_state": {
237                            "last_started": "2026-03-07T15:00:00Z",
238                            "last_finished": "2026-03-07T15:00:09Z",
239                            "last_result": "success",
240                            "last_error": null
241                        }
242                    }
243                ],
244                "orphans": [
245                    {
246                        "id": "s9",
247                        "backend": "systemd_user"
248                    }
249                ]
250            })
251        );
252    }
253}