celestia_tendermint/
timeout.rs1use 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#[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 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 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}