islabtech_upw_sensor_v1/measurements/
serde_impl.rs

1///! implements [`Serialize`] and [`Deserialize`] for types defined in [`measurements`] module
2use serde::{
3    de::{self, Visitor},
4    ser::SerializeStruct,
5    Deserialize, Serialize,
6};
7
8use super::{Conductivity, Measurement, SuccessfulMeasurement, Temperature};
9
10macro_rules! impl_ser {
11    ($t:ty) => {
12        impl Serialize for $t {
13            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
14            where
15                S: serde::Serializer,
16            {
17                let mut state = serializer.serialize_struct(stringify!($t), 4)?;
18                state.serialize_field("epoch_timestamp", &self.timestamp.timestamp())?;
19                state.serialize_field(
20                    "epoch_microseconds",
21                    &self.timestamp.timestamp_subsec_micros(),
22                )?;
23                state.serialize_field("conductivity", &self.conductivity)?;
24                state.serialize_field("temperature", &self.temperature)?;
25                state.end()
26            }
27        }
28    };
29}
30impl_ser!(Measurement);
31impl_ser!(SuccessfulMeasurement);
32
33macro_rules! impl_de {
34    ($t:ident, $cond_t: ty, $temp_t: ty) => {
35        impl<'de> Deserialize<'de> for $t {
36            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37            where
38                D: serde::Deserializer<'de>,
39            {
40                #[derive(Deserialize)]
41                #[serde(field_identifier, rename_all = "snake_case")]
42                enum Field {
43                    EpochTimestamp,
44                    EpochMicroseconds,
45                    Conductivity,
46                    Temperature,
47                    #[serde(other)]
48                    Ignore,
49                }
50                struct MeasurementVisitor;
51                impl<'de> Visitor<'de> for MeasurementVisitor {
52                    type Value = $t;
53                    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
54                        formatter.write_str("struct Measurement")
55                    }
56                    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
57                    where
58                        A: serde::de::MapAccess<'de>,
59                    {
60                        let mut seconds: Option<i64> = None;
61                        let mut micros: Option<u32> = None;
62                        let mut cond: Option<$cond_t> = None;
63                        let mut temp: Option<$temp_t> = None;
64                        while let Some(key) = map.next_key()? {
65                            match key {
66                                Field::EpochTimestamp => seconds = Some(map.next_value()?),
67                                Field::EpochMicroseconds => micros = Some(map.next_value()?),
68                                Field::Conductivity => cond = Some(map.next_value()?),
69                                Field::Temperature => temp = Some(map.next_value()?),
70                                Field::Ignore => {
71                                    let _: serde::de::IgnoredAny = map.next_value()?;
72                                }
73                            }
74                        }
75                        Ok($t {
76                            timestamp: chrono::DateTime::from_timestamp(
77                                seconds
78                                    .ok_or_else(|| de::Error::missing_field("epoch_timestamp"))?,
79                                micros.ok_or_else(|| {
80                                    de::Error::missing_field("epoch_microseconds")
81                                })? * 1000,
82                            )
83                            .ok_or_else(|| de::Error::custom("invalid timestamp"))?,
84                            conductivity: cond
85                                .ok_or_else(|| de::Error::missing_field("conductivity"))?,
86                            temperature: temp
87                                .ok_or_else(|| de::Error::missing_field("temperature"))?,
88                        })
89                    }
90                }
91
92                deserializer.deserialize_map(MeasurementVisitor)
93            }
94        }
95    };
96}
97
98impl_de!(Measurement, Option<Conductivity>, Option<Temperature>);
99impl_de!(SuccessfulMeasurement, Conductivity, Temperature);
100
101#[cfg(test)]
102mod tests {
103    use crate::measurements::{Measurement, SuccessfulMeasurement};
104    fn example_timestamp() -> chrono::DateTime<chrono::Utc> {
105        chrono::DateTime::from_timestamp(123, 456789 * 1000).unwrap()
106    }
107    fn json(cond: &str, temp: &str) -> String {
108        format!("{{\"epoch_timestamp\":123,\"epoch_microseconds\":456789,\"conductivity\":{cond},\"temperature\":{temp}}}")
109    }
110    fn serde_pairs() -> Vec<(String, Measurement, Option<SuccessfulMeasurement>)> {
111        vec![
112            (
113                json("1.234", "12.34"),
114                Measurement {
115                    timestamp: example_timestamp(),
116                    conductivity: Some(1.234.into()),
117                    temperature: Some(12.34.into()),
118                },
119                Some(SuccessfulMeasurement {
120                    timestamp: example_timestamp(),
121                    conductivity: 1.234.into(),
122                    temperature: 12.34.into(),
123                }),
124            ),
125            (
126                json("null", "null"),
127                Measurement {
128                    timestamp: example_timestamp(),
129                    conductivity: None,
130                    temperature: None,
131                },
132                None,
133            ),
134            (
135                json("1.234", "null"),
136                Measurement {
137                    timestamp: example_timestamp(),
138                    conductivity: Some(1.234.into()),
139                    temperature: None,
140                },
141                None,
142            ),
143            (
144                json("null", "12.34"),
145                Measurement {
146                    timestamp: example_timestamp(),
147                    conductivity: None,
148                    temperature: Some(12.34.into()),
149                },
150                None,
151            ),
152        ]
153    }
154
155    #[test]
156    fn serialize_measurement() {
157        for (json, measurement, successful_measurement) in serde_pairs().iter() {
158            assert_eq!(
159                serde_json::to_string(measurement).expect("can not serialize Measurement"),
160                *json,
161            );
162            if let Some(successful_measurement) = successful_measurement {
163                assert_eq!(
164                    serde_json::to_string(successful_measurement)
165                        .expect("can not serialize SuccessfulMeasurement"),
166                    *json
167                );
168            }
169        }
170    }
171
172    #[test]
173    fn deserialize_measurement() {
174        for (serialized, measurement, successful_measurement) in serde_pairs().iter() {
175            assert_eq!(
176                serde_json::from_str::<Measurement>(serialized)
177                    .expect("can not deserialize Measurement"),
178                *measurement,
179                "deserializing `Measurement`"
180            );
181            assert_eq!(
182                serde_json::from_str::<SuccessfulMeasurement>(serialized).ok(),
183                *successful_measurement,
184                "deserializing `Measurement`"
185            );
186        }
187    }
188
189    #[test]
190    fn deserialize_incomplete_measurement() {
191        let incomplete_json = vec![
192            (
193                r#"{"epoch_timestamp":123, "epoch_microseconds":456789, "conductivity": null }"#,
194                "missing temperature",
195            ),
196            (
197                r#"{"epoch_timestamp":123, "epoch_microseconds":456789, "temperature": null }"#,
198                "missing conductivity",
199            ),
200            (
201                r#"{"epoch_timestamp":123, "conductivity":null, "temperature": null }"#,
202                "missing epoch microseconds",
203            ),
204            (
205                r#"{"epoch_microseconds":123, "conductivity":null, "temperature": null }"#,
206                "missing epoch seconds",
207            ),
208        ];
209        for (json, description) in incomplete_json.iter() {
210            let err = serde_json::from_str::<Measurement>(json).expect_err(
211                format!("Measurement {description} succeeded but it should not").as_str(),
212            );
213            assert!(
214                err.is_data(),
215                "Measurement {description} threw wrong error type"
216            );
217        }
218    }
219}