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() -> String {
59        "PrettyDuration".to_owned()
60    }
61
62    fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
63        String::json_schema(r#gen)
64    }
65}
66
67impl Display for DurationUnit {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        match self {
70            DurationUnit::Seconds => write!(f, "s"),
71            DurationUnit::Minutes => write!(f, "m"),
72            DurationUnit::Hours => write!(f, "h"),
73            DurationUnit::Days => write!(f, "d"),
74        }
75    }
76}
77
78impl FromStr for DurationUnit {
79    type Err = ();
80
81    fn from_str(s: &str) -> Result<Self, Self::Err> {
82        match s {
83            "s" | "S" => Ok(Self::Seconds),
84            "m" | "M" => Ok(Self::Minutes),
85            "h" | "H" => Ok(Self::Hours),
86            "d" | "D" => Ok(Self::Days),
87            _ => Err(()),
88        }
89    }
90}
91
92impl Display for PrettyDuration {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        write!(f, "{}{}", self.amount, self.unit)
95    }
96}
97
98impl Debug for PrettyDuration {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        <Self as Display>::fmt(self, f)
101    }
102}
103
104impl FromStr for PrettyDuration {
105    type Err = ();
106
107    fn from_str(s: &str) -> Result<Self, Self::Err> {
108        let (amount_str, unit_str) = s.split_at_checked(s.len() - 1).ok_or(())?;
109        Ok(Self {
110            unit: unit_str.parse()?,
111            amount: amount_str.parse().map_err(|_| ())?,
112        })
113    }
114}
115
116impl Serialize for PrettyDuration {
117    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
118    where
119        S: serde::Serializer,
120    {
121        serializer.serialize_str(&self.to_string())
122    }
123}
124
125impl<'de> Deserialize<'de> for PrettyDuration {
126    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127    where
128        D: serde::Deserializer<'de>,
129    {
130        let repr: Cow<'de, str> = Cow::deserialize(deserializer)?;
131        repr.parse()
132            .map_err(|()| D::Error::custom("Failed to parse value as a duration"))
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    pub fn pretty_duration_serialize() {
142        assert_eq!(
143            PrettyDuration {
144                unit: DurationUnit::Seconds,
145                amount: 1234
146            }
147            .to_string(),
148            "1234s"
149        );
150        assert_eq!(
151            PrettyDuration {
152                unit: DurationUnit::Minutes,
153                amount: 345
154            }
155            .to_string(),
156            "345m"
157        );
158        assert_eq!(
159            PrettyDuration {
160                unit: DurationUnit::Hours,
161                amount: 56
162            }
163            .to_string(),
164            "56h"
165        );
166        assert_eq!(
167            PrettyDuration {
168                unit: DurationUnit::Days,
169                amount: 7
170            }
171            .to_string(),
172            "7d"
173        );
174    }
175
176    #[test]
177    pub fn pretty_duration_deserialize() {
178        fn assert_deserializes_to(repr1: &str, repr2: &str, unit: DurationUnit, amount: u64) {
179            let duration = PrettyDuration { unit, amount };
180            assert_eq!(duration, repr1.parse().unwrap());
181            assert_eq!(duration, repr2.parse().unwrap());
182        }
183
184        assert_deserializes_to("12s", "12S", DurationUnit::Seconds, 12);
185        assert_deserializes_to("34m", "34M", DurationUnit::Minutes, 34);
186        assert_deserializes_to("56h", "56H", DurationUnit::Hours, 56);
187        assert_deserializes_to("7d", "7D", DurationUnit::Days, 7);
188    }
189
190    #[test]
191    #[should_panic]
192    pub fn cant_parse_nagative_duration() {
193        _ = "-12s".parse::<PrettyDuration>().unwrap();
194    }
195}