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