pbjson_types/
duration.rs

1use crate::Duration;
2use serde::de::Visitor;
3use serde::Serialize;
4
5impl TryFrom<Duration> for std::time::Duration {
6    type Error = std::num::TryFromIntError;
7
8    fn try_from(value: Duration) -> Result<Self, Self::Error> {
9        Ok(Self::new(
10            value.seconds.try_into()?,
11            value.nanos.try_into()?,
12        ))
13    }
14}
15
16impl From<std::time::Duration> for Duration {
17    fn from(value: std::time::Duration) -> Self {
18        Self {
19            seconds: value.as_secs() as _,
20            nanos: value.subsec_nanos() as _,
21        }
22    }
23}
24
25impl Serialize for Duration {
26    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27    where
28        S: serde::Serializer,
29    {
30        if self.seconds != 0 && self.nanos != 0 && (self.nanos < 0) != (self.seconds < 0) {
31            return Err(serde::ser::Error::custom("Duration has inconsistent signs"));
32        }
33
34        let mut s = if self.seconds == 0 {
35            if self.nanos < 0 {
36                "-0".to_string()
37            } else {
38                "0".to_string()
39            }
40        } else {
41            self.seconds.to_string()
42        };
43
44        if self.nanos != 0 {
45            s.push('.');
46            let f = match split_nanos(self.nanos.unsigned_abs()) {
47                (millis, 0, 0) => format!("{:03}", millis),
48                (millis, micros, 0) => format!("{:03}{:03}", millis, micros),
49                (millis, micros, nanos) => format!("{:03}{:03}{:03}", millis, micros, nanos),
50            };
51            s.push_str(&f);
52        }
53
54        s.push('s');
55        serializer.serialize_str(&s)
56    }
57}
58
59struct DurationVisitor;
60
61impl<'de> Visitor<'de> for DurationVisitor {
62    type Value = Duration;
63
64    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        formatter.write_str("a duration string")
66    }
67
68    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
69    where
70        E: serde::de::Error,
71    {
72        let s = s
73            .strip_suffix('s')
74            .ok_or_else(|| serde::de::Error::custom("missing 's' suffix"))?;
75
76        let (negative, s) = match s.strip_prefix('-') {
77            Some(s) => (true, s),
78            None => (false, s),
79        };
80
81        let duration = match s.split_once('.') {
82            Some((seconds_str, decimal_str)) => {
83                let exp = 9_u32
84                    .checked_sub(decimal_str.len() as u32)
85                    .ok_or_else(|| serde::de::Error::custom("too many decimal places"))?;
86
87                let pow = 10_u32.pow(exp);
88                let seconds = seconds_str.parse().map_err(serde::de::Error::custom)?;
89                let decimal: u32 = decimal_str.parse().map_err(serde::de::Error::custom)?;
90
91                Duration {
92                    seconds,
93                    nanos: (decimal * pow) as i32,
94                }
95            }
96            None => Duration {
97                seconds: s.parse().map_err(serde::de::Error::custom)?,
98                nanos: 0,
99            },
100        };
101
102        Ok(match negative {
103            true => Duration {
104                seconds: -duration.seconds,
105                nanos: -duration.nanos,
106            },
107            false => duration,
108        })
109    }
110}
111
112impl<'de> serde::Deserialize<'de> for Duration {
113    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114    where
115        D: serde::Deserializer<'de>,
116    {
117        deserializer.deserialize_str(DurationVisitor)
118    }
119}
120
121/// Splits nanoseconds into whole milliseconds, microseconds, and nanoseconds
122fn split_nanos(mut nanos: u32) -> (u32, u32, u32) {
123    let millis = nanos / 1_000_000;
124    nanos -= millis * 1_000_000;
125    let micros = nanos / 1_000;
126    nanos -= micros * 1_000;
127    (millis, micros, nanos)
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_duration() {
136        let verify = |duration: &Duration, expected: &str| {
137            assert_eq!(serde_json::to_string(duration).unwrap().as_str(), expected);
138            assert_eq!(
139                &serde_json::from_str::<Duration>(expected).unwrap(),
140                duration
141            )
142        };
143
144        let duration = Duration {
145            seconds: 0,
146            nanos: 0,
147        };
148        verify(&duration, "\"0s\"");
149
150        let duration = Duration {
151            seconds: 0,
152            nanos: 123,
153        };
154        verify(&duration, "\"0.000000123s\"");
155
156        let duration = Duration {
157            seconds: 0,
158            nanos: 123456,
159        };
160        verify(&duration, "\"0.000123456s\"");
161
162        let duration = Duration {
163            seconds: 0,
164            nanos: 123456789,
165        };
166        verify(&duration, "\"0.123456789s\"");
167
168        let duration = Duration {
169            seconds: 0,
170            nanos: -67088,
171        };
172        verify(&duration, "\"-0.000067088s\"");
173
174        let duration = Duration {
175            seconds: 121,
176            nanos: 3454,
177        };
178        verify(&duration, "\"121.000003454s\"");
179
180        let duration = Duration {
181            seconds: -90,
182            nanos: -2456301,
183        };
184        verify(&duration, "\"-90.002456301s\"");
185
186        let duration = Duration {
187            seconds: -90,
188            nanos: 234,
189        };
190        serde_json::to_string(&duration).unwrap_err();
191
192        let duration = Duration {
193            seconds: 90,
194            nanos: -234,
195        };
196        serde_json::to_string(&duration).unwrap_err();
197
198        serde_json::from_str::<Duration>("90.1234567891s").unwrap_err();
199    }
200}