google_apis_common/
serde.rs

1pub mod duration {
2    use std::fmt::Formatter;
3    use std::str::FromStr;
4
5    use serde::{Deserialize, Deserializer};
6    use serde_with::{DeserializeAs, SerializeAs};
7
8    use chrono::Duration;
9
10    const MAX_SECONDS: i64 = 315576000000i64;
11
12    #[derive(Debug)]
13    enum ParseDurationError {
14        MissingSecondSuffix,
15        NanosTooSmall,
16        ParseIntError(std::num::ParseIntError),
17        SecondOverflow { seconds: i64, max_seconds: i64 },
18        SecondUnderflow { seconds: i64, min_seconds: i64 },
19        DurationSeconds { seconds: i64 },
20    }
21
22    impl From<std::num::ParseIntError> for ParseDurationError {
23        fn from(pie: std::num::ParseIntError) -> Self {
24            ParseDurationError::ParseIntError(pie)
25        }
26    }
27
28    impl std::fmt::Display for ParseDurationError {
29        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
30            match self {
31                ParseDurationError::MissingSecondSuffix => write!(f, "'s' suffix was not present"),
32                ParseDurationError::NanosTooSmall => {
33                    write!(f, "more than 9 digits of second precision required")
34                }
35                ParseDurationError::ParseIntError(pie) => write!(f, "{pie:?}"),
36                ParseDurationError::SecondOverflow {
37                    seconds,
38                    max_seconds,
39                } => write!(
40                    f,
41                    "seconds overflow (got {seconds}, maximum seconds possible {max_seconds})"
42                ),
43                ParseDurationError::SecondUnderflow {
44                    seconds,
45                    min_seconds,
46                } => write!(
47                    f,
48                    "seconds underflow (got {seconds}, minimum seconds possible {min_seconds})"
49                ),
50                ParseDurationError::DurationSeconds { seconds } => {
51                    write!(f, "Could not create a duration from {seconds}")
52                }
53            }
54        }
55    }
56
57    impl std::error::Error for ParseDurationError {}
58
59    fn duration_from_str(s: &str) -> Result<Duration, ParseDurationError> {
60        // TODO: Test strings like -.s, -0.0s
61        let value = match s.strip_suffix('s') {
62            None => return Err(ParseDurationError::MissingSecondSuffix),
63            Some(v) => v,
64        };
65
66        let (seconds, nanoseconds) = if let Some((seconds, nanos)) = value.split_once('.') {
67            let is_neg = seconds.starts_with('-');
68            let seconds = i64::from_str(seconds)?;
69            let nano_magnitude = nanos.chars().filter(|c| c.is_ascii_digit()).count() as u32;
70            if nano_magnitude > 9 {
71                // not enough precision to model the remaining digits
72                return Err(ParseDurationError::NanosTooSmall);
73            }
74
75            // u32::from_str prevents negative nanos (eg '0.-12s) -> lossless conversion to i32
76            // 10_u32.pow(...) scales number to appropriate # of nanoseconds
77            let nanos = u32::from_str(nanos)? as i32;
78
79            let mut nanos = nanos * 10_i32.pow(9 - nano_magnitude);
80            if is_neg {
81                nanos = -nanos;
82            }
83            (seconds, nanos)
84        } else {
85            (i64::from_str(value)?, 0)
86        };
87
88        if seconds >= MAX_SECONDS {
89            Err(ParseDurationError::SecondOverflow {
90                seconds,
91                max_seconds: MAX_SECONDS,
92            })
93        } else if seconds <= -MAX_SECONDS {
94            Err(ParseDurationError::SecondUnderflow {
95                seconds,
96                min_seconds: -MAX_SECONDS,
97            })
98        } else {
99            Ok(Duration::try_seconds(seconds)
100                .ok_or(ParseDurationError::DurationSeconds { seconds })?
101                + Duration::nanoseconds(nanoseconds.into()))
102        }
103    }
104
105    pub fn to_string(duration: &Duration) -> String {
106        let seconds = duration.num_seconds();
107        let nanoseconds = (*duration
108            - Duration::try_seconds(seconds).expect("Seconds in bounds to create Duration from"))
109        .num_nanoseconds()
110        .expect("absolute number of nanoseconds is less than 1 billion")
111            as i32;
112        if nanoseconds != 0 {
113            if seconds == 0 && nanoseconds.is_negative() {
114                format!("-0.{:0>9}s", nanoseconds.abs())
115            } else {
116                format!("{}.{:0>9}s", seconds, nanoseconds.abs())
117            }
118        } else {
119            format!("{seconds}s")
120        }
121    }
122
123    pub struct Wrapper;
124
125    impl SerializeAs<Duration> for Wrapper {
126        fn serialize_as<S>(value: &Duration, s: S) -> Result<S::Ok, S::Error>
127        where
128            S: serde::Serializer,
129        {
130            s.serialize_str(&to_string(value))
131        }
132    }
133
134    impl<'de> DeserializeAs<'de, Duration> for Wrapper {
135        fn deserialize_as<D>(deserializer: D) -> Result<Duration, D::Error>
136        where
137            D: Deserializer<'de>,
138        {
139            let s = Deserialize::deserialize(deserializer)?;
140            duration_from_str(s).map_err(serde::de::Error::custom)
141        }
142    }
143}
144
145pub mod standard_base64 {
146    use std::borrow::Cow;
147
148    use base64::Engine as _;
149    use serde::{Deserialize, Deserializer, Serializer};
150    use serde_with::{DeserializeAs, SerializeAs};
151
152    pub struct Wrapper;
153
154    pub fn to_string(bytes: &Vec<u8>) -> String {
155        base64::prelude::BASE64_STANDARD.encode(bytes)
156    }
157
158    impl SerializeAs<Vec<u8>> for Wrapper {
159        fn serialize_as<S>(value: &Vec<u8>, s: S) -> Result<S::Ok, S::Error>
160        where
161            S: Serializer,
162        {
163            s.serialize_str(&to_string(value))
164        }
165    }
166
167    impl<'de> DeserializeAs<'de, Vec<u8>> for Wrapper {
168        fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
169        where
170            D: Deserializer<'de>,
171        {
172            let s: Cow<str> = Deserialize::deserialize(deserializer)?;
173            match base64::prelude::BASE64_STANDARD.decode(s.as_ref()) {
174                Ok(decoded) => Ok(decoded),
175                Err(first_err) => match base64::prelude::BASE64_URL_SAFE.decode(s.as_ref()) {
176                    Ok(decoded) => Ok(decoded),
177                    Err(_) => Err(serde::de::Error::custom(first_err)),
178                },
179            }
180        }
181    }
182}
183
184pub mod urlsafe_base64 {
185    use std::borrow::Cow;
186
187    use base64::Engine as _;
188    use serde::{Deserialize, Deserializer, Serializer};
189    use serde_with::{DeserializeAs, SerializeAs};
190
191    pub struct Wrapper;
192
193    pub fn to_string(bytes: &Vec<u8>) -> String {
194        base64::prelude::BASE64_URL_SAFE.encode(bytes)
195    }
196
197    impl SerializeAs<Vec<u8>> for Wrapper {
198        fn serialize_as<S>(value: &Vec<u8>, s: S) -> Result<S::Ok, S::Error>
199        where
200            S: Serializer,
201        {
202            s.serialize_str(&to_string(value))
203        }
204    }
205
206    impl<'de> DeserializeAs<'de, Vec<u8>> for Wrapper {
207        fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
208        where
209            D: Deserializer<'de>,
210        {
211            let s: Cow<str> = Deserialize::deserialize(deserializer)?;
212            match base64::prelude::BASE64_URL_SAFE.decode(s.as_ref()) {
213                Ok(decoded) => Ok(decoded),
214                Err(first_err) => match base64::prelude::BASE64_STANDARD.decode(s.as_ref()) {
215                    Ok(decoded) => Ok(decoded),
216                    Err(_) => Err(serde::de::Error::custom(first_err)),
217                },
218            }
219        }
220    }
221}
222
223pub fn datetime_to_string(datetime: &chrono::DateTime<chrono::offset::Utc>) -> String {
224    datetime.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
225}
226
227#[cfg(test)]
228mod tests {
229    use serde::{Deserialize, Serialize};
230    use serde_with::{serde_as, DisplayFromStr};
231
232    use super::{duration, standard_base64, urlsafe_base64};
233
234    #[serde_as]
235    #[derive(Serialize, Deserialize, Debug, PartialEq)]
236    struct DurationWrapper {
237        #[serde_as(as = "Option<duration::Wrapper>")]
238        duration: Option<chrono::Duration>,
239    }
240
241    #[serde_as]
242    #[derive(Serialize, Deserialize, Debug, PartialEq)]
243    struct Base64URLSafeWrapper {
244        #[serde_as(as = "Option<urlsafe_base64::Wrapper>")]
245        bytes: Option<Vec<u8>>,
246    }
247
248    #[serde_as]
249    #[derive(Serialize, Deserialize, Debug, PartialEq)]
250    struct Base64StandardWrapper {
251        #[serde_as(as = "Option<standard_base64::Wrapper>")]
252        bytes: Option<Vec<u8>>,
253    }
254
255    #[serde_as]
256    #[derive(Serialize, Deserialize, Debug, PartialEq)]
257    struct I64Wrapper {
258        #[serde_as(as = "Option<DisplayFromStr>")]
259        num: Option<i64>,
260    }
261
262    #[test]
263    fn test_duration_de_success_cases() {
264        let durations = [
265            ("-0.2s", -200_000_000),
266            ("0.000000001s", 1),
267            ("999.999999999s", 999_999_999_999),
268            ("129s", 129_000_000_000),
269            ("0.123456789s", 123_456_789),
270        ];
271        for (repr, nanos) in durations.into_iter() {
272            let wrapper: DurationWrapper =
273                serde_json::from_str(&format!("{{\"duration\": \"{repr}\"}}")).unwrap();
274            assert_eq!(
275                Some(nanos),
276                wrapper.duration.unwrap().num_nanoseconds(),
277                "parsed \"{repr}\" expecting Duration with {nanos}ns",
278            );
279        }
280    }
281
282    #[test]
283    fn test_duration_de_failure_cases() {
284        let durations = ["1.-3s", "1.1111111111s", "1.2"];
285        for repr in durations.into_iter() {
286            assert!(
287                serde_json::from_str::<DurationWrapper>(&format!("{{\"duration\": \"{repr}\"}}"))
288                    .is_err(),
289                "parsed \"{repr}\" expecting err",
290            );
291        }
292    }
293
294    #[test]
295    fn test_duration_ser_success_cases() {
296        let durations = [
297            -200_000_000,
298            1,
299            999_999_999_999,
300            129_000_000_000,
301            123_456_789,
302        ];
303
304        for nanos in durations.into_iter() {
305            let wrapper = DurationWrapper {
306                duration: Some(chrono::Duration::nanoseconds(nanos)),
307            };
308            let s = serde_json::to_string(&wrapper);
309            assert!(s.is_ok(), "Could not serialize {nanos}ns");
310            let s = s.unwrap();
311            assert_eq!(
312                wrapper,
313                serde_json::from_str(&s).unwrap(),
314                "round trip should return same duration"
315            );
316        }
317    }
318
319    #[test]
320    fn standard_base64_de_success_cases() {
321        let wrapper: Base64StandardWrapper =
322            serde_json::from_str(r#"{"bytes": "cVhabzk6U21uOkN+MylFWFRJMVFLdEh2MShmVHp9"}"#)
323                .unwrap();
324        assert_eq!(
325            Some(b"qXZo9:Smn:C~3)EXTI1QKtHv1(fTz}".as_slice()),
326            wrapper.bytes.as_deref()
327        );
328    }
329
330    #[test]
331    fn standard_base64_de_reader_success_cases() {
332        let standard: Base64StandardWrapper = serde_json::from_reader(
333            r#"{"bytes": "cVhabzk6U21uOkN+MylFWFRJMVFLdEh2MShmVHp9"}"#.as_bytes(),
334        )
335        .unwrap();
336        assert_eq!(
337            Some(b"qXZo9:Smn:C~3)EXTI1QKtHv1(fTz}".as_slice()),
338            standard.bytes.as_deref()
339        );
340    }
341
342    #[test]
343    fn urlsafe_base64_de_success_cases() {
344        let wrapper: Base64URLSafeWrapper =
345            serde_json::from_str(r#"{"bytes": "aGVsbG8gd29ybGQ="}"#).unwrap();
346        assert_eq!(Some(b"hello world".as_slice()), wrapper.bytes.as_deref());
347    }
348
349    #[test]
350    fn urlsafe_base64_de_reader_success_cases() {
351        let wrapper: Base64URLSafeWrapper =
352            serde_json::from_reader(r#"{"bytes": "aGVsbG8gd29ybGQ="}"#.as_bytes()).unwrap();
353        assert_eq!(Some(b"hello world".as_slice()), wrapper.bytes.as_deref());
354    }
355
356    #[test]
357    fn urlsafe_base64_de_standard_success_cases() {
358        let wrapper: Base64URLSafeWrapper =  // Expect URL-safe base64 accepts standard encoding
359            serde_json::from_reader(r#"{"bytes": "REE/P0V+Nz4oIWtH"}"#.as_bytes()).unwrap();
360        assert_eq!(Some(b"DA??E~7>(!kG".as_slice()), wrapper.bytes.as_deref());
361    }
362
363    #[test]
364    fn urlsafe_base64_de_failure_cases() {
365        assert!(
366            serde_json::from_str::<Base64URLSafeWrapper>(r#"{"bytes": "aGVsbG8gd29ybG&Q"}"#)
367                .is_err()
368        );
369    }
370
371    #[test]
372    fn standard_base64_de_urlsafe_success_cases() {
373        let wrapper: Base64URLSafeWrapper =  // Expect standard base64 accepts url-safe encoding
374            serde_json::from_reader(r#"{"bytes": "REE_P0V-Nz4oIWtH"}"#.as_bytes()).unwrap();
375        assert_eq!(Some(b"DA??E~7>(!kG".as_slice()), wrapper.bytes.as_deref());
376    }
377
378    #[test]
379    fn standard_base64_de_failure_cases() {
380        assert!(serde_json::from_str::<Base64StandardWrapper>(r#"{"bytes": "%"}"#).is_err());
381    }
382
383    #[test]
384    fn urlsafe_base64_roundtrip() {
385        let wrapper = Base64URLSafeWrapper {
386            bytes: Some(b"Hello world!".to_vec()),
387        };
388        let s = serde_json::to_string(&wrapper).expect("serialization of bytes infallible");
389        assert_eq!(
390            wrapper,
391            serde_json::from_str::<Base64URLSafeWrapper>(&s).unwrap()
392        );
393    }
394
395    #[test]
396    fn standard_base64_roundtrip() {
397        let wrapper = Base64StandardWrapper {
398            bytes: Some(b"Hello world!".to_vec()),
399        };
400        let s = serde_json::to_string(&wrapper).expect("serialization of bytes infallible");
401        assert_eq!(
402            wrapper,
403            serde_json::from_str::<Base64StandardWrapper>(&s).unwrap()
404        );
405    }
406
407    #[test]
408    fn num_roundtrip() {
409        let wrapper = I64Wrapper {
410            num: Some(i64::MAX),
411        };
412
413        let json_repr = &serde_json::to_string(&wrapper);
414        assert!(json_repr.is_ok(), "serialization should succeed");
415        assert_eq!(
416            wrapper,
417            serde_json::from_str(&format!("{{\"num\": \"{}\"}}", i64::MAX)).unwrap()
418        );
419        assert_eq!(
420            wrapper,
421            serde_json::from_str(json_repr.as_ref().unwrap()).unwrap(),
422            "round trip should succeed"
423        );
424    }
425
426    #[test]
427    fn test_empty_wrapper() {
428        assert_eq!(
429            DurationWrapper { duration: None },
430            serde_json::from_str("{}").unwrap()
431        );
432        assert_eq!(
433            Base64URLSafeWrapper { bytes: None },
434            serde_json::from_str("{}").unwrap()
435        );
436        assert_eq!(
437            Base64StandardWrapper { bytes: None },
438            serde_json::from_str("{}").unwrap()
439        );
440
441        assert_eq!(
442            I64Wrapper { num: None },
443            serde_json::from_str("{}").unwrap()
444        );
445    }
446}