kuma_client/models/
maintenance.rs

1//! Models related to Uptime Kuma maintenances
2
3use crate::deserialize::{
4    DeserializeBoolLenient, DeserializeNumberLenient, SerializeDateRange, SerializeTimeRange,
5};
6use serde::{
7    de::{self, MapAccess, Visitor},
8    ser::SerializeStruct,
9    Deserialize, Deserializer, Serialize, Serializer,
10};
11use serde_inline_default::serde_inline_default;
12use serde_json::Value;
13use serde_repr::{Deserialize_repr, Serialize_repr};
14use serde_with::{serde_as, skip_serializing_none};
15use std::{collections::HashMap, fmt};
16use time::{PrimitiveDateTime, Time};
17
18include!(concat!(env!("OUT_DIR"), "/timezones.rs"));
19
20#[serde_inline_default]
21#[skip_serializing_none]
22#[serde_as]
23#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
24pub struct MaintenanceMonitor {
25    #[serde(rename = "id")]
26    pub id: Option<i32>,
27
28    #[serde(rename = "pathName")]
29    pub path_name: Option<String>,
30}
31
32#[serde_inline_default]
33#[skip_serializing_none]
34#[serde_as]
35#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
36pub struct MaintenanceStatusPage {
37    #[serde(rename = "id")]
38    pub id: Option<i32>,
39
40    #[serde(rename = "name")]
41    pub name: Option<String>,
42}
43
44#[derive(Clone, Serialize_repr, Deserialize_repr, PartialEq, Debug)]
45#[repr(u8)]
46pub enum Weekday {
47    Monday = 1,
48    Tuesday = 2,
49    Wednesday = 3,
50    Thursday = 4,
51    Friday = 5,
52    Saturday = 6,
53    Sunday = 0,
54}
55
56#[derive(PartialEq, Eq, Clone, Debug)]
57pub enum DayOfMonth {
58    Day(u8),
59    LastDay,
60}
61
62impl<'de> Deserialize<'de> for DayOfMonth {
63    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64    where
65        D: Deserializer<'de>,
66    {
67        let value: Value = Deserialize::deserialize(deserializer)?;
68
69        match value {
70            Value::Number(n) if n.is_u64() => {
71                Ok(DayOfMonth::Day(n.as_u64().unwrap().try_into().unwrap()))
72            }
73            Value::String(s) if s == "lastDay1" => Ok(DayOfMonth::LastDay),
74            _ => Err(serde::de::Error::custom("Invalid DayOfMonth format")),
75        }
76    }
77}
78
79impl Serialize for DayOfMonth {
80    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81    where
82        S: Serializer,
83    {
84        match self {
85            DayOfMonth::Day(day) => serializer.serialize_u8(*day),
86            DayOfMonth::LastDay => serializer.serialize_str("lastDay1"),
87        }
88    }
89}
90
91#[skip_serializing_none]
92#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
93pub struct TimeSlot {
94    #[serde(rename = "startDate")]
95    pub start_date: Option<String>,
96
97    #[serde(rename = "endDate")]
98    pub end_date: Option<String>,
99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum TimeZoneOption {
103    SameAsServer(Option<TimeZone>),
104    UTC,
105    TimeZone(TimeZone),
106}
107
108impl Serialize for TimeZoneOption {
109    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110    where
111        S: serde::Serializer,
112    {
113        let (timezone, timezone_option, timezone_offset) = match self {
114            TimeZoneOption::SameAsServer(tz) => (
115                tz.as_ref()
116                    .map(|tz| tz.identifier().to_owned())
117                    .unwrap_or("UTC".to_owned()),
118                "SAME_AS_SERVER".to_owned(),
119                tz.as_ref()
120                    .map(|tz| tz.utc_offset().to_owned())
121                    .unwrap_or("+00:00".to_owned()),
122            ),
123            TimeZoneOption::UTC => ("UTC".to_owned(), "UTC".to_owned(), "+00:00".to_owned()),
124            TimeZoneOption::TimeZone(timezone) => (
125                timezone.identifier().to_owned(),
126                timezone.identifier().to_owned(),
127                timezone.utc_offset().to_owned(),
128            ),
129        };
130
131        let mut ser_struct = serializer.serialize_struct("TimeZone", 3)?;
132        ser_struct.serialize_field("timezone", &timezone)?;
133        ser_struct.serialize_field("timezoneOption", &timezone_option)?;
134        ser_struct.serialize_field("timezoneOffset", &timezone_offset)?;
135        ser_struct.end()
136    }
137}
138
139impl<'de> Deserialize<'de> for TimeZoneOption {
140    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
141    where
142        D: Deserializer<'de>,
143    {
144        enum Field {
145            TimeZone,
146            TimeZoneOption,
147            TimeZoneOffset,
148        }
149
150        impl<'de> Deserialize<'de> for Field {
151            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
152            where
153                D: Deserializer<'de>,
154            {
155                struct FieldVisitor;
156
157                impl<'de> Visitor<'de> for FieldVisitor {
158                    type Value = Field;
159
160                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
161                        formatter.write_str("`timezone`, `timezoneOption` or `timezoneOffset`")
162                    }
163
164                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
165                    where
166                        E: de::Error,
167                    {
168                        match value {
169                            "timezone" => Ok(Field::TimeZone),
170                            "timezoneOption" => Ok(Field::TimeZoneOption),
171                            "timezoneOffset" => Ok(Field::TimeZoneOffset),
172                            _ => Err(de::Error::unknown_field(value, FIELDS)),
173                        }
174                    }
175                }
176
177                deserializer.deserialize_identifier(FieldVisitor)
178            }
179        }
180
181        struct TimeZoneOptionVisitor;
182
183        impl<'de> Visitor<'de> for TimeZoneOptionVisitor {
184            type Value = TimeZoneOption;
185
186            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
187                formatter.write_str("struct TimeZoneOption")
188            }
189
190            fn visit_map<V>(self, mut map: V) -> Result<TimeZoneOption, V::Error>
191            where
192                V: MapAccess<'de>,
193            {
194                let mut timezone_identifier: Option<String> = None;
195                let mut timezone_option: Option<String> = None;
196                let mut timezone_offset: Option<String> = None;
197                while let Some(key) = map.next_key()? {
198                    match key {
199                        Field::TimeZone => {
200                            if timezone_identifier.is_some() {
201                                return Err(de::Error::duplicate_field("timezone"));
202                            }
203                            timezone_identifier = Some(map.next_value()?);
204                        }
205                        Field::TimeZoneOption => {
206                            if timezone_option.is_some() {
207                                return Err(de::Error::duplicate_field("timezoneOption"));
208                            }
209                            timezone_option = Some(map.next_value()?);
210                        }
211                        Field::TimeZoneOffset => {
212                            if timezone_offset.is_some() {
213                                return Err(de::Error::duplicate_field("timezoneOffset"));
214                            }
215                            timezone_offset = Some(map.next_value()?);
216                        }
217                    }
218                }
219                let timezone_identifier =
220                    timezone_identifier.ok_or_else(|| de::Error::missing_field("timezone"))?;
221                let timezone_option =
222                    timezone_option.ok_or_else(|| de::Error::missing_field("timezoneOption"))?;
223                let _timezone_offset =
224                    timezone_offset.ok_or_else(|| de::Error::missing_field("timezoneOffset"))?;
225
226                let timezone = TimeZone::from_str(&timezone_identifier).ok_or_else(|| {
227                    de::Error::invalid_value(
228                        de::Unexpected::Str(&timezone_identifier),
229                        &"a valid timezone identifier",
230                    )
231                })?;
232
233                Ok(match timezone_option.as_ref() {
234                    "SAME_AS_SERVER" => TimeZoneOption::SameAsServer(Some(timezone)),
235                    "UTC" => TimeZoneOption::UTC,
236                    _ => TimeZoneOption::TimeZone(timezone),
237                })
238            }
239        }
240
241        const FIELDS: &'static [&'static str] = &["timezone", "timezoneOption", "timezoneOffset"];
242        deserializer.deserialize_struct("TimeZoneOption", FIELDS, TimeZoneOptionVisitor)
243    }
244}
245
246#[derive(Clone, Debug, PartialEq)]
247pub struct Range<T> {
248    pub start: T,
249    pub end: T,
250}
251
252#[serde_inline_default]
253#[skip_serializing_none]
254#[serde_as]
255#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
256pub struct MaintenanceCommon {
257    #[serde(rename = "id")]
258    #[serde_as(as = "Option<DeserializeNumberLenient>")]
259    pub id: Option<i32>,
260
261    #[serde(rename = "title")]
262    pub title: Option<String>,
263
264    #[serde(rename = "description")]
265    pub description: Option<String>,
266
267    #[serde(rename = "active")]
268    #[serde_inline_default(Some(true))]
269    #[serde_as(as = "Option<DeserializeBoolLenient>")]
270    pub active: Option<bool>,
271
272    #[serde(rename = "status")]
273    pub status: Option<String>,
274
275    #[serde(rename = "monitors")]
276    #[serde(default)]
277    pub monitors: Option<Vec<MaintenanceMonitor>>,
278
279    #[serde(rename = "statusPages")]
280    #[serde(default)]
281    pub status_pages: Option<Vec<MaintenanceStatusPage>>,
282}
283crate::default_from_serde!(MaintenanceCommon);
284
285#[serde_inline_default]
286#[skip_serializing_none]
287#[serde_as]
288#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
289pub struct MaintenanceSchedule {
290    #[serde(rename = "dateRange")]
291    #[serde_as(as = "SerializeDateRange")]
292    #[serialize_always]
293    pub date_range: Option<Range<PrimitiveDateTime>>,
294
295    #[serde(rename = "timeRange")]
296    #[serde_as(as = "Option<SerializeTimeRange>")]
297    pub time_range: Option<Range<Time>>,
298
299    #[serde(flatten)]
300    #[serde(rename = "timezone")]
301    pub timezone: Option<TimeZoneOption>,
302}
303crate::default_from_serde!(MaintenanceSchedule);
304
305#[serde_inline_default]
306#[skip_serializing_none]
307#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
308pub struct MaintenanceCron {
309    #[serde(rename = "cron")]
310    #[serde_inline_default(Some("30 3 * * *".to_owned()))]
311    pub cron: Option<String>,
312
313    #[serde(rename = "durationMinutes")]
314    #[serde_inline_default(Some(60.0))]
315    pub duration_minutes: Option<f64>,
316}
317crate::default_from_serde!(MaintenanceCron);
318
319#[serde_inline_default]
320#[skip_serializing_none]
321#[serde_as]
322#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
323pub struct MaintenanceRecurringInterval {
324    #[serde(rename = "intervalDay")]
325    pub interval: Option<u8>,
326
327    #[serde(rename = "timeslotList")]
328    #[serde(default)]
329    pub timeslots: Vec<TimeSlot>,
330}
331crate::default_from_serde!(MaintenanceRecurringInterval);
332
333#[serde_inline_default]
334#[skip_serializing_none]
335#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
336pub struct MaintenanceRecurringWeekday {
337    #[serde(rename = "timeslotList")]
338    #[serde(default)]
339    pub timeslots: Vec<TimeSlot>,
340
341    #[serde(rename = "weekdays")]
342    #[serde(default)]
343    pub weekdays: Vec<Weekday>,
344}
345crate::default_from_serde!(MaintenanceRecurringWeekday);
346
347#[serde_inline_default]
348#[skip_serializing_none]
349#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
350pub struct MaintenanceRecurringDayOfMonth {
351    #[serde(rename = "daysOfMonth")]
352    #[serde(default)]
353    pub days_of_month: Vec<DayOfMonth>,
354
355    #[serde(rename = "timeslotList")]
356    #[serde(default)]
357    pub timeslots: Vec<TimeSlot>,
358}
359crate::default_from_serde!(MaintenanceRecurringDayOfMonth);
360
361#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
362#[serde(tag = "strategy")]
363pub enum Maintenance {
364    #[serde(rename = "manual")]
365    Manual {
366        #[serde(flatten)]
367        common: MaintenanceCommon,
368    },
369
370    #[serde(rename = "single")]
371    Single {
372        #[serde(flatten)]
373        common: MaintenanceCommon,
374        #[serde(flatten)]
375        schedule: MaintenanceSchedule,
376    },
377
378    #[serde(rename = "cron")]
379    Cron {
380        #[serde(flatten)]
381        common: MaintenanceCommon,
382        #[serde(flatten)]
383        schedule: MaintenanceSchedule,
384        #[serde(flatten)]
385        cron: MaintenanceCron,
386    },
387
388    #[serde(rename = "recurring-interval")]
389    RecurringInterval {
390        #[serde(flatten)]
391        common: MaintenanceCommon,
392        #[serde(flatten)]
393        schedule: MaintenanceSchedule,
394        #[serde(flatten)]
395        recurring_interval: MaintenanceRecurringInterval,
396    },
397
398    #[serde(rename = "recurring-weekday")]
399    RecurringWeekday {
400        #[serde(flatten)]
401        common: MaintenanceCommon,
402        #[serde(flatten)]
403        schedule: MaintenanceSchedule,
404        #[serde(flatten)]
405        recurring_weekday: MaintenanceRecurringWeekday,
406    },
407
408    #[serde(rename = "recurring-day-of-month")]
409    RecurringDayOfMonth {
410        #[serde(flatten)]
411        common: MaintenanceCommon,
412        #[serde(flatten)]
413        schedule: MaintenanceSchedule,
414        #[serde(flatten)]
415        recurring_day_of_month: MaintenanceRecurringDayOfMonth,
416    },
417}
418
419impl Maintenance {
420    pub fn common(&self) -> &MaintenanceCommon {
421        match self {
422            Maintenance::Manual { common } => common,
423            Maintenance::Single { common, .. } => common,
424            Maintenance::Cron { common, .. } => common,
425            Maintenance::RecurringInterval { common, .. } => common,
426            Maintenance::RecurringWeekday { common, .. } => common,
427            Maintenance::RecurringDayOfMonth { common, .. } => common,
428        }
429    }
430    pub fn common_mut(&mut self) -> &mut MaintenanceCommon {
431        match self {
432            Maintenance::Manual { common } => common,
433            Maintenance::Single { common, .. } => common,
434            Maintenance::Cron { common, .. } => common,
435            Maintenance::RecurringInterval { common, .. } => common,
436            Maintenance::RecurringWeekday { common, .. } => common,
437            Maintenance::RecurringDayOfMonth { common, .. } => common,
438        }
439    }
440}
441
442pub type MaintenanceList = HashMap<String, Maintenance>;