wasmer_config/app/
pretty_duration.rs

1use std::{
2    borrow::Cow,
3    fmt::{Debug, Display},
4    str::FromStr,
5    time::Duration,
6};
7
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize, de::Error};
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash)]
12pub struct PrettyDuration {
13    unit: DurationUnit,
14    amount: u64,
15}
16
17#[derive(Clone, Copy, PartialEq, Eq, Hash)]
18pub enum DurationUnit {
19    Seconds,
20    Minutes,
21    Hours,
22    Days,
23}
24
25impl PrettyDuration {
26    pub fn as_duration(&self) -> Duration {
27        match self.unit {
28            DurationUnit::Seconds => Duration::from_secs(self.amount),
29            DurationUnit::Minutes => Duration::from_secs(self.amount * 60),
30            DurationUnit::Hours => Duration::from_secs(self.amount * 60 * 60),
31            DurationUnit::Days => Duration::from_secs(self.amount * 60 * 60 * 24),
32        }
33    }
34}
35
36impl Default for PrettyDuration {
37    fn default() -> Self {
38        Self {
39            unit: DurationUnit::Seconds,
40            amount: 0,
41        }
42    }
43}
44
45impl PartialOrd for PrettyDuration {
46    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
47        Some(self.cmp(other))
48    }
49}
50
51impl Ord for PrettyDuration {
52    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
53        self.as_duration().cmp(&other.as_duration())
54    }
55}
56
57impl JsonSchema for PrettyDuration {
58    fn schema_name() -> Cow<'static, str> {
59        Cow::Borrowed("PrettyDuration")
60    }
61
62    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
63        String::json_schema(generator)
64    }
65
66    fn inline_schema() -> bool {
67        false
68    }
69
70    fn schema_id() -> Cow<'static, str> {
71        Self::schema_name()
72    }
73}
74
75impl Display for DurationUnit {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            DurationUnit::Seconds => write!(f, "s"),
79            DurationUnit::Minutes => write!(f, "m"),
80            DurationUnit::Hours => write!(f, "h"),
81            DurationUnit::Days => write!(f, "d"),
82        }
83    }
84}
85
86impl FromStr for DurationUnit {
87    type Err = ();
88
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        match s {
91            "s" | "S" => Ok(Self::Seconds),
92            "m" | "M" => Ok(Self::Minutes),
93            "h" | "H" => Ok(Self::Hours),
94            "d" | "D" => Ok(Self::Days),
95            _ => Err(()),
96        }
97    }
98}
99
100impl Display for PrettyDuration {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        write!(f, "{}{}", self.amount, self.unit)
103    }
104}
105
106impl Debug for PrettyDuration {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        <Self as Display>::fmt(self, f)
109    }
110}
111
112impl FromStr for PrettyDuration {
113    type Err = ();
114
115    fn from_str(s: &str) -> Result<Self, Self::Err> {
116        let (amount_str, unit_str) = s.split_at_checked(s.len() - 1).ok_or(())?;
117        Ok(Self {
118            unit: unit_str.parse()?,
119            amount: amount_str.parse().map_err(|_| ())?,
120        })
121    }
122}
123
124impl Serialize for PrettyDuration {
125    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126    where
127        S: serde::Serializer,
128    {
129        serializer.serialize_str(&self.to_string())
130    }
131}
132
133impl<'de> Deserialize<'de> for PrettyDuration {
134    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
135    where
136        D: serde::Deserializer<'de>,
137    {
138        let repr: Cow<'de, str> = Cow::deserialize(deserializer)?;
139        repr.parse()
140            .map_err(|()| D::Error::custom("Failed to parse value as a duration"))
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    pub fn pretty_duration_serialize() {
150        assert_eq!(
151            PrettyDuration {
152                unit: DurationUnit::Seconds,
153                amount: 1234
154            }
155            .to_string(),
156            "1234s"
157        );
158        assert_eq!(
159            PrettyDuration {
160                unit: DurationUnit::Minutes,
161                amount: 345
162            }
163            .to_string(),
164            "345m"
165        );
166        assert_eq!(
167            PrettyDuration {
168                unit: DurationUnit::Hours,
169                amount: 56
170            }
171            .to_string(),
172            "56h"
173        );
174        assert_eq!(
175            PrettyDuration {
176                unit: DurationUnit::Days,
177                amount: 7
178            }
179            .to_string(),
180            "7d"
181        );
182    }
183
184    #[test]
185    pub fn pretty_duration_deserialize() {
186        fn assert_deserializes_to(repr1: &str, repr2: &str, unit: DurationUnit, amount: u64) {
187            let duration = PrettyDuration { unit, amount };
188            assert_eq!(duration, repr1.parse().unwrap());
189            assert_eq!(duration, repr2.parse().unwrap());
190        }
191
192        assert_deserializes_to("12s", "12S", DurationUnit::Seconds, 12);
193        assert_deserializes_to("34m", "34M", DurationUnit::Minutes, 34);
194        assert_deserializes_to("56h", "56H", DurationUnit::Hours, 56);
195        assert_deserializes_to("7d", "7D", DurationUnit::Days, 7);
196    }
197
198    #[test]
199    #[should_panic]
200    pub fn cant_parse_nagative_duration() {
201        _ = "-12s".parse::<PrettyDuration>().unwrap();
202    }
203}