config_types/
duration.rs

1use regex::Regex;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::ops::Deref;
4use std::time::Duration;
5
6#[derive(Debug, Clone, Default)]
7pub struct DurationConf(Duration);
8
9impl DurationConf {
10    pub fn new(duration: Duration) -> Self {
11        Self(duration)
12    }
13}
14
15impl<'de> Deserialize<'de> for DurationConf {
16    fn deserialize<D>(deserializer: D) -> Result<DurationConf, D::Error>
17    where
18        D: Deserializer<'de>,
19    {
20        let s = String::deserialize(deserializer)?;
21        let duration = parse_duration(&s).map_err(serde::de::Error::custom)?;
22        Ok(DurationConf(duration))
23    }
24}
25
26impl Serialize for DurationConf {
27    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
28    where
29        S: Serializer,
30    {
31        let duration_str = format_duration(self.0);
32        serializer.serialize_str(&duration_str)
33    }
34}
35
36impl Deref for DurationConf {
37    type Target = Duration;
38
39    fn deref(&self) -> &Self::Target {
40        &self.0
41    }
42}
43
44impl Into<Duration> for DurationConf {
45    fn into(self) -> Duration {
46        self.0
47    }
48}
49
50fn parse_duration(s: &str) -> Result<Duration, String> {
51    let re = Regex::new(r"^(\d+)\s*(ns|us|ms|s|m|h)$").map_err(|_| "Invalid regex")?;
52    let caps = re.captures(s).ok_or_else(|| format!("Invalid duration: {}", s))?;
53    let value: u64 = caps[1].parse().map_err(|_| "Invalid number")?;
54    match &caps[2] {
55        "ns" => Ok(Duration::from_nanos(value)),
56        "us" => Ok(Duration::from_micros(value)),
57        "ms" => Ok(Duration::from_millis(value)),
58        "s" => Ok(Duration::from_secs(value)),
59        "m" => Ok(Duration::from_secs(value * 60)),
60        "h" => Ok(Duration::from_secs(value * 3600)),
61        _ => Err("Invalid unit".to_string()),
62    }
63}
64
65fn format_duration(duration: Duration) -> String {
66    if duration.as_nanos() % 1_000 != 0 {
67        format!("{}ns", duration.as_nanos())
68    } else if duration.as_micros() % 1_000 != 0 {
69        format!("{}us", duration.as_micros())
70    } else if duration.as_millis() % 1_000 != 0 {
71        format!("{}ms", duration.as_millis())
72    } else if duration.as_secs() % 60 != 0 {
73        format!("{}s", duration.as_secs())
74    } else if duration.as_secs() % 3600 != 0 {
75        format!("{}m", duration.as_secs() / 60)
76    } else {
77        format!("{}h", duration.as_secs() / 3600)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_parse_duration() {
87        assert_eq!(parse_duration("1ns").unwrap(), Duration::from_nanos(1));
88        assert_eq!(parse_duration("1us").unwrap(), Duration::from_micros(1));
89        assert_eq!(parse_duration("1ms").unwrap(), Duration::from_millis(1));
90        assert_eq!(parse_duration("1s").unwrap(), Duration::from_secs(1));
91        assert_eq!(parse_duration("1m").unwrap(), Duration::from_secs(60));
92        assert_eq!(parse_duration("1h").unwrap(), Duration::from_secs(3600));
93    }
94
95    #[test]
96    fn test_format_duration() {
97        assert_eq!(format_duration(Duration::from_nanos(1)), "1ns");
98        assert_eq!(format_duration(Duration::from_micros(1)), "1us");
99        assert_eq!(format_duration(Duration::from_millis(1)), "1ms");
100        assert_eq!(format_duration(Duration::from_secs(1)), "1s");
101        assert_eq!(format_duration(Duration::from_secs(60)), "1m");
102        assert_eq!(format_duration(Duration::from_secs(3600)), "1h");
103    }
104}