ralertsinua_models/
alert.rs

1use crate::{alert_type::*, LocationType};
2use serde::{Deserialize, Serialize};
3use serde_with::{serde_as, skip_serializing_none, DisplayFromStr};
4use time::OffsetDateTime;
5
6#[skip_serializing_none]
7#[serde_as]
8#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
9pub struct Alert {
10    pub id: i32,
11    pub location_title: String,
12    #[serde_as(as = "DisplayFromStr")] // Serialize with Display, deserialize with FromStr
13    pub location_type: LocationType,
14    #[serde(with = "time::serde::iso8601")]
15    pub started_at: OffsetDateTime,
16    #[serde(with = "time::serde::iso8601")]
17    pub updated_at: OffsetDateTime,
18    // #[serde(with = "time::serde::iso8601")]
19    pub finished_at: Option<String>, // TODO: parse Option to OffsetDateTime
20    #[serde_as(as = "DisplayFromStr")] // Serialize with Display, deserialize with FromStr
21    pub alert_type: AlertType,
22    pub location_oblast: String,
23    #[serde(with = "into_int")]
24    pub location_uid: i32,
25    pub location_oblast_uid: i32,
26    pub notes: Option<String>,
27    #[serde(default)]
28    pub country: Option<String>,
29    #[serde(default)]
30    pub calculated: Option<bool>,
31}
32
33pub mod into_int {
34    use super::*;
35    use serde::{de::*, Serializer};
36
37    pub fn serialize<S>(value: &i32, serializer: S) -> Result<S::Ok, S::Error>
38    where
39        S: Serializer,
40    {
41        serializer.serialize_str(&value.to_string())
42    }
43
44    pub fn deserialize<'de, D>(deserializer: D) -> Result<i32, D::Error>
45    where
46        D: Deserializer<'de>,
47    {
48        let s = String::deserialize(deserializer)?;
49        s.parse::<i32>().map_err(Error::custom)
50    }
51}
52
53impl Alert {
54    pub fn get_alert_duration(&self) -> std::time::Duration {
55        let now = OffsetDateTime::now_utc();
56        let offset_duration = now - self.started_at;
57        offset_duration.try_into().unwrap()
58    }
59}
60
61mod tests {
62
63    #[test]
64    fn test_alert_deserialization() {
65        use super::*;
66        use serde_json::json;
67
68        let data = json!({
69            "alert_type": "air_raid",
70            "calculated": null,
71            "country": null,
72            "finished_at": null,
73            "id": 8757,
74            "location_oblast": "Луганська область",
75            "location_oblast_uid": 16,
76            "location_title": "Луганська область",
77            "location_type": "oblast",
78            "location_uid": "16",
79            "notes": null,
80            "started_at": "2022-04-04T16:45:39.000Z",
81            "updated_at": "2023-10-29T18:22:37.357Z"
82        });
83
84        let alert = serde_json::from_value(data);
85        if alert.is_err() {
86            let err = alert.err().unwrap();
87            panic!("Failed to deserialize Alert: {:?}", err);
88        }
89        let alert: Alert = alert.unwrap();
90
91        assert_eq!(alert.id, 8757);
92        assert_eq!(alert.location_title, "Луганська область");
93        assert_eq!(alert.location_type, LocationType::Oblast);
94        assert_eq!(alert.location_oblast, "Луганська область");
95        assert_eq!(alert.location_uid, 16);
96        assert_eq!(alert.location_oblast_uid, 16);
97        assert_eq!(alert.alert_type, AlertType::AirRaid);
98        assert_eq!(alert.notes, None);
99        assert_eq!(alert.country, None);
100        assert_eq!(alert.calculated, None);
101        assert_eq!(alert.started_at.unix_timestamp(), 1_649_090_739);
102        assert_eq!(alert.updated_at.unix_timestamp(), 1_698_603_757);
103        assert_eq!(alert.finished_at, None);
104    }
105}