pbjson_types/
duration.rs

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