kanade_shared/ipc/
maintenance.rs1use serde::{Deserialize, Serialize};
11
12#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
16pub struct MaintenanceListParams {
17 #[serde(default = "default_window_days")]
20 pub window_days: u32,
21}
22
23impl Default for MaintenanceListParams {
24 fn default() -> Self {
25 Self {
26 window_days: default_window_days(),
27 }
28 }
29}
30
31fn default_window_days() -> u32 {
32 7
33}
34
35#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
36pub struct MaintenanceListResult {
37 pub items: Vec<MaintenanceItem>,
38}
39
40#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
42pub struct MaintenanceItem {
43 pub schedule_id: String,
45 pub manifest_id: String,
48 pub display_name: String,
51 pub next_fire_at: chrono::DateTime<chrono::Utc>,
54 #[serde(default)]
59 pub deferrable: bool,
60}
61
62#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
68pub struct MaintenanceDeferParams {
69 pub schedule_id: String,
70 pub duration: DeferDuration,
71}
72
73#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone, Copy, PartialEq, Eq, Hash)]
85#[non_exhaustive]
86pub enum DeferDuration {
87 #[serde(rename = "15m")]
89 M15,
90 #[serde(rename = "30m")]
92 M30,
93 #[serde(rename = "1h")]
95 H1,
96}
97
98impl DeferDuration {
99 pub fn as_duration(self) -> chrono::Duration {
101 match self {
102 Self::M15 => chrono::Duration::minutes(15),
103 Self::M30 => chrono::Duration::minutes(30),
104 Self::H1 => chrono::Duration::hours(1),
105 }
106 }
107}
108
109#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
110pub struct MaintenanceDeferResult {
111 pub new_fire_at: chrono::DateTime<chrono::Utc>,
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use chrono::TimeZone;
121
122 #[test]
123 fn params_default_window_is_seven_days() {
124 let p = MaintenanceListParams::default();
125 assert_eq!(p.window_days, 7);
126 let p: MaintenanceListParams = serde_json::from_str("{}").unwrap();
127 assert_eq!(p.window_days, 7);
128 }
129
130 #[test]
131 fn item_with_deferrable_round_trips() {
132 let t = chrono::Utc
133 .with_ymd_and_hms(2026, 5, 25, 14, 30, 0)
134 .unwrap();
135 let i = MaintenanceItem {
136 schedule_id: "weekly-reboot".into(),
137 manifest_id: "reboot".into(),
138 display_name: "再起動".into(),
139 next_fire_at: t,
140 deferrable: true,
141 };
142 let json = serde_json::to_string(&i).unwrap();
143 let back: MaintenanceItem = serde_json::from_str(&json).unwrap();
144 assert_eq!(back.schedule_id, i.schedule_id);
145 assert_eq!(back.display_name, "再起動");
146 assert_eq!(back.next_fire_at, t);
147 assert!(back.deferrable);
148 }
149
150 #[test]
151 fn defer_duration_wire_matches_spec_2_1_humantime() {
152 for (variant, expected) in [
158 (DeferDuration::M15, "\"15m\""),
159 (DeferDuration::M30, "\"30m\""),
160 (DeferDuration::H1, "\"1h\""),
161 ] {
162 let s = serde_json::to_string(&variant).unwrap();
163 assert_eq!(s, expected, "encode {variant:?}");
164 let back: DeferDuration = serde_json::from_str(expected).unwrap();
165 assert_eq!(back, variant, "round-trip {expected}");
166 }
167 }
168
169 #[test]
170 fn defer_duration_as_duration_matches_spec_table() {
171 assert_eq!(
172 DeferDuration::M15.as_duration(),
173 chrono::Duration::minutes(15)
174 );
175 assert_eq!(
176 DeferDuration::M30.as_duration(),
177 chrono::Duration::minutes(30)
178 );
179 assert_eq!(DeferDuration::H1.as_duration(), chrono::Duration::hours(1));
180 }
181
182 #[test]
183 fn defer_result_round_trips() {
184 let t = chrono::Utc.with_ymd_and_hms(2026, 5, 24, 15, 0, 0).unwrap();
185 let r = MaintenanceDeferResult { new_fire_at: t };
186 let json = serde_json::to_string(&r).unwrap();
187 let back: MaintenanceDeferResult = serde_json::from_str(&json).unwrap();
188 assert_eq!(back.new_fire_at, t);
189 }
190
191 #[test]
192 fn item_deferrable_defaults_to_false() {
193 let wire = r#"{
194 "schedule_id":"x","manifest_id":"y","display_name":"z",
195 "next_fire_at":"2026-05-24T00:00:00Z"
196 }"#;
197 let i: MaintenanceItem = serde_json::from_str(wire).unwrap();
198 assert!(!i.deferrable);
199 }
200}