tendermint_proto/google/protobuf/
duration.rs1use core::convert::TryFrom;
8use core::fmt;
9
10use prost::Name;
11
12use crate::prelude::*;
13
14use super::type_url::type_url_for;
15use super::PACKAGE;
16
17#[derive(Copy, Clone, PartialEq, Eq, ::prost::Message)]
24#[cfg_attr(feature = "json-schema", derive(::schemars::JsonSchema))]
25pub struct Duration {
26 #[prost(int64, tag = "1")]
30 pub seconds: i64,
31 #[prost(int32, tag = "2")]
38 pub nanos: i32,
39}
40
41impl Name for Duration {
42 const PACKAGE: &'static str = PACKAGE;
43 const NAME: &'static str = "Duration";
44
45 fn type_url() -> String {
46 type_url_for::<Self>()
47 }
48}
49
50const NANOS_PER_SECOND: i32 = 1_000_000_000;
51const NANOS_MAX: i32 = NANOS_PER_SECOND - 1;
52
53impl Duration {
54 pub fn normalize(&mut self) {
56 if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
58 if let Some(seconds) = self
59 .seconds
60 .checked_add((self.nanos / NANOS_PER_SECOND) as i64)
61 {
62 self.seconds = seconds;
63 self.nanos %= NANOS_PER_SECOND;
64 } else if self.nanos < 0 {
65 self.seconds = i64::MIN;
67 self.nanos = -NANOS_MAX;
68 } else {
69 self.seconds = i64::MAX;
71 self.nanos = NANOS_MAX;
72 }
73 }
74
75 if self.seconds < 0 && self.nanos > 0 {
77 if let Some(seconds) = self.seconds.checked_add(1) {
78 self.seconds = seconds;
79 self.nanos -= NANOS_PER_SECOND;
80 } else {
81 debug_assert_eq!(self.seconds, i64::MAX);
83 self.nanos = NANOS_MAX;
84 }
85 } else if self.seconds > 0 && self.nanos < 0 {
86 if let Some(seconds) = self.seconds.checked_sub(1) {
87 self.seconds = seconds;
88 self.nanos += NANOS_PER_SECOND;
89 } else {
90 debug_assert_eq!(self.seconds, i64::MIN);
92 self.nanos = -NANOS_MAX;
93 }
94 }
95 }
96}
97
98#[derive(Debug, PartialEq, Eq)]
100#[non_exhaustive]
101pub enum DurationError {
102 NegativeDuration(core::time::Duration),
106
107 OutOfRange,
112}
113
114impl fmt::Display for DurationError {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 DurationError::NegativeDuration(duration) => {
118 write!(f, "failed to convert negative duration: {duration:?}")
119 },
120 DurationError::OutOfRange => {
121 write!(f, "failed to convert duration out of range")
122 },
123 }
124 }
125}
126
127#[cfg(feature = "std")]
128impl std::error::Error for DurationError {}
129
130impl TryFrom<Duration> for core::time::Duration {
131 type Error = DurationError;
132
133 fn try_from(mut duration: Duration) -> Result<core::time::Duration, DurationError> {
135 duration.normalize();
136 if duration.seconds >= 0 && duration.nanos >= 0 {
137 Ok(core::time::Duration::new(
138 duration.seconds as u64,
139 duration.nanos as u32,
140 ))
141 } else {
142 Err(DurationError::NegativeDuration(core::time::Duration::new(
143 (-duration.seconds) as u64,
144 (-duration.nanos) as u32,
145 )))
146 }
147 }
148}
149
150impl TryFrom<core::time::Duration> for Duration {
151 type Error = DurationError;
152
153 fn try_from(duration: core::time::Duration) -> Result<Duration, DurationError> {
155 let seconds = i64::try_from(duration.as_secs()).map_err(|_| DurationError::OutOfRange)?;
156 let nanos = duration.subsec_nanos() as i32;
157
158 let mut duration = Duration { seconds, nanos };
159 duration.normalize();
160 Ok(duration)
161 }
162}
163
164impl serde::Serialize for Duration {
165 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166 where
167 S: serde::Serializer,
168 {
169 if self.seconds != 0 && self.nanos != 0 && (self.nanos < 0) != (self.seconds < 0) {
170 return Err(serde::ser::Error::custom("Duration has inconsistent signs"));
171 }
172
173 let mut s = if self.seconds == 0 {
174 if self.nanos < 0 {
175 "-0".to_string()
176 } else {
177 "0".to_string()
178 }
179 } else {
180 self.seconds.to_string()
181 };
182
183 if self.nanos != 0 {
184 s.push('.');
185 let f = match split_nanos(self.nanos.unsigned_abs()) {
186 (millis, 0, 0) => format!("{:03}", millis),
187 (millis, micros, 0) => format!("{:03}{:03}", millis, micros),
188 (millis, micros, nanos) => format!("{:03}{:03}{:03}", millis, micros, nanos),
189 };
190 s.push_str(&f);
191 }
192
193 s.push('s');
194 serializer.serialize_str(&s)
195 }
196}
197
198struct DurationVisitor;
199
200impl serde::de::Visitor<'_> for DurationVisitor {
201 type Value = Duration;
202
203 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204 formatter.write_str("a duration string")
205 }
206
207 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
208 where
209 E: serde::de::Error,
210 {
211 let s = s
212 .strip_suffix('s')
213 .ok_or_else(|| serde::de::Error::custom("missing 's' suffix"))?;
214
215 let (negative, s) = match s.strip_prefix('-') {
216 Some(s) => (true, s),
217 None => (false, s),
218 };
219
220 let duration = match s.split_once('.') {
221 Some((seconds_str, decimal_str)) => {
222 let exp = 9_u32
223 .checked_sub(decimal_str.len() as u32)
224 .ok_or_else(|| serde::de::Error::custom("too many decimal places"))?;
225
226 let pow = 10_u32.pow(exp);
227 let seconds = seconds_str.parse().map_err(serde::de::Error::custom)?;
228 let decimal: u32 = decimal_str.parse().map_err(serde::de::Error::custom)?;
229
230 Duration {
231 seconds,
232 nanos: (decimal * pow) as i32,
233 }
234 },
235 None => Duration {
236 seconds: s.parse().map_err(serde::de::Error::custom)?,
237 nanos: 0,
238 },
239 };
240
241 Ok(match negative {
242 true => Duration {
243 seconds: -duration.seconds,
244 nanos: -duration.nanos,
245 },
246 false => duration,
247 })
248 }
249}
250
251impl<'de> serde::Deserialize<'de> for Duration {
252 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
253 where
254 D: serde::Deserializer<'de>,
255 {
256 deserializer.deserialize_str(DurationVisitor)
257 }
258}
259
260fn split_nanos(mut nanos: u32) -> (u32, u32, u32) {
262 let millis = nanos / 1_000_000;
263 nanos -= millis * 1_000_000;
264 let micros = nanos / 1_000;
265 nanos -= micros * 1_000;
266 (millis, micros, nanos)
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn test_duration() {
275 let verify = |duration: &Duration, expected: &str| {
276 assert_eq!(serde_json::to_string(duration).unwrap().as_str(), expected);
277 assert_eq!(
278 &serde_json::from_str::<Duration>(expected).unwrap(),
279 duration
280 )
281 };
282
283 let duration = Duration {
284 seconds: 0,
285 nanos: 0,
286 };
287 verify(&duration, "\"0s\"");
288
289 let duration = Duration {
290 seconds: 0,
291 nanos: 123,
292 };
293 verify(&duration, "\"0.000000123s\"");
294
295 let duration = Duration {
296 seconds: 0,
297 nanos: 123456,
298 };
299 verify(&duration, "\"0.000123456s\"");
300
301 let duration = Duration {
302 seconds: 0,
303 nanos: 123456789,
304 };
305 verify(&duration, "\"0.123456789s\"");
306
307 let duration = Duration {
308 seconds: 0,
309 nanos: -67088,
310 };
311 verify(&duration, "\"-0.000067088s\"");
312
313 let duration = Duration {
314 seconds: 121,
315 nanos: 3454,
316 };
317 verify(&duration, "\"121.000003454s\"");
318
319 let duration = Duration {
320 seconds: -90,
321 nanos: -2456301,
322 };
323 verify(&duration, "\"-90.002456301s\"");
324
325 let duration = Duration {
326 seconds: -90,
327 nanos: 234,
328 };
329 serde_json::to_string(&duration).unwrap_err();
330
331 let duration = Duration {
332 seconds: 90,
333 nanos: -234,
334 };
335 serde_json::to_string(&duration).unwrap_err();
336
337 serde_json::from_str::<Duration>("90.1234567891s").unwrap_err();
338 }
339}