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}