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 #[serde(other)]
100 Unknown,
101}
102
103impl DeferDuration {
104 pub fn as_duration(self) -> chrono::Duration {
106 match self {
107 Self::M15 => chrono::Duration::minutes(15),
108 Self::Unknown => {
113 tracing::warn!("unknown DeferDuration variant from a newer peer; treating as 15m");
114 chrono::Duration::minutes(15)
115 }
116 Self::M30 => chrono::Duration::minutes(30),
117 Self::H1 => chrono::Duration::hours(1),
118 }
119 }
120}
121
122#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
123pub struct MaintenanceDeferResult {
124 pub new_fire_at: chrono::DateTime<chrono::Utc>,
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use chrono::TimeZone;
134
135 #[test]
136 fn params_default_window_is_seven_days() {
137 let p = MaintenanceListParams::default();
138 assert_eq!(p.window_days, 7);
139 let p: MaintenanceListParams = serde_json::from_str("{}").unwrap();
140 assert_eq!(p.window_days, 7);
141 }
142
143 #[test]
144 fn item_with_deferrable_round_trips() {
145 let t = chrono::Utc
146 .with_ymd_and_hms(2026, 5, 25, 14, 30, 0)
147 .unwrap();
148 let i = MaintenanceItem {
149 schedule_id: "weekly-reboot".into(),
150 manifest_id: "reboot".into(),
151 display_name: "再起動".into(),
152 next_fire_at: t,
153 deferrable: true,
154 };
155 let json = serde_json::to_string(&i).unwrap();
156 let back: MaintenanceItem = serde_json::from_str(&json).unwrap();
157 assert_eq!(back.schedule_id, i.schedule_id);
158 assert_eq!(back.display_name, "再起動");
159 assert_eq!(back.next_fire_at, t);
160 assert!(back.deferrable);
161 }
162
163 #[test]
164 fn defer_duration_wire_matches_spec_2_1_humantime() {
165 for (variant, expected) in [
171 (DeferDuration::M15, "\"15m\""),
172 (DeferDuration::M30, "\"30m\""),
173 (DeferDuration::H1, "\"1h\""),
174 ] {
175 let s = serde_json::to_string(&variant).unwrap();
176 assert_eq!(s, expected, "encode {variant:?}");
177 let back: DeferDuration = serde_json::from_str(expected).unwrap();
178 assert_eq!(back, variant, "round-trip {expected}");
179 }
180 }
181
182 #[test]
183 fn defer_duration_as_duration_matches_spec_table() {
184 assert_eq!(
185 DeferDuration::M15.as_duration(),
186 chrono::Duration::minutes(15)
187 );
188 assert_eq!(
189 DeferDuration::M30.as_duration(),
190 chrono::Duration::minutes(30)
191 );
192 assert_eq!(DeferDuration::H1.as_duration(), chrono::Duration::hours(1));
193 }
194
195 #[test]
196 fn defer_result_round_trips() {
197 let t = chrono::Utc.with_ymd_and_hms(2026, 5, 24, 15, 0, 0).unwrap();
198 let r = MaintenanceDeferResult { new_fire_at: t };
199 let json = serde_json::to_string(&r).unwrap();
200 let back: MaintenanceDeferResult = serde_json::from_str(&json).unwrap();
201 assert_eq!(back.new_fire_at, t);
202 }
203
204 #[test]
205 fn item_deferrable_defaults_to_false() {
206 let wire = r#"{
207 "schedule_id":"x","manifest_id":"y","display_name":"z",
208 "next_fire_at":"2026-05-24T00:00:00Z"
209 }"#;
210 let i: MaintenanceItem = serde_json::from_str(wire).unwrap();
211 assert!(!i.deferrable);
212 }
213}