tendermint/
timeout.rs

1use core::{fmt, ops::Deref, str::FromStr, time::Duration};
2
3use serde::{de, de::Error as _, ser, Deserialize, Serialize};
4
5use crate::{error::Error, prelude::*};
6
7/// Timeout durations
8#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
9pub struct Timeout(Duration);
10
11impl Deref for Timeout {
12    type Target = Duration;
13
14    fn deref(&self) -> &Duration {
15        &self.0
16    }
17}
18
19impl From<Duration> for Timeout {
20    fn from(duration: Duration) -> Timeout {
21        Timeout(duration)
22    }
23}
24
25impl From<Timeout> for Duration {
26    fn from(timeout: Timeout) -> Duration {
27        timeout.0
28    }
29}
30
31impl FromStr for Timeout {
32    type Err = Error;
33
34    fn from_str(s: &str) -> Result<Self, Self::Err> {
35        // Timeouts are either 'ms' or 's', and should always end with 's'
36        if s.len() < 2 || !s.ends_with('s') {
37            return Err(Error::parse("invalid units".to_string()));
38        }
39
40        let units = match s.chars().nth(s.len() - 2) {
41            Some('m') => "ms",
42            Some('0'..='9') => "s",
43            _ => return Err(Error::parse("invalid units".to_string())),
44        };
45
46        let numeric_part = s.chars().take(s.len() - units.len()).collect::<String>();
47
48        let numeric_value = numeric_part
49            .parse::<u64>()
50            .map_err(|e| Error::parse_int(numeric_part, e))?;
51
52        let duration = match units {
53            "s" => Duration::from_secs(numeric_value),
54            "ms" => Duration::from_millis(numeric_value),
55            _ => unreachable!(),
56        };
57
58        Ok(Timeout(duration))
59    }
60}
61
62impl fmt::Display for Timeout {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}ms", self.as_millis())
65    }
66}
67
68impl<'de> Deserialize<'de> for Timeout {
69    /// Parse `Timeout` from string ending in `s` or `ms`
70    fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
71        let string = String::deserialize(deserializer)?;
72        string
73            .parse()
74            .map_err(|_| D::Error::custom(format!("invalid timeout value: {:?}", &string)))
75    }
76}
77
78impl Serialize for Timeout {
79    fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
80        self.to_string().serialize(serializer)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::Timeout;
87    use crate::error;
88
89    #[test]
90    fn parse_seconds() {
91        let timeout = "123s".parse::<Timeout>().unwrap();
92        assert_eq!(timeout.as_secs(), 123);
93    }
94
95    #[test]
96    fn parse_milliseconds() {
97        let timeout = "123ms".parse::<Timeout>().unwrap();
98        assert_eq!(timeout.as_millis(), 123);
99    }
100
101    #[test]
102    fn reject_no_units() {
103        match "123".parse::<Timeout>().unwrap_err().detail() {
104            error::ErrorDetail::Parse(_) => {},
105            _ => panic!("expected parse error to be returned"),
106        }
107    }
108}