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}