Skip to main content

canic_host/duration/
mod.rs

1use thiserror::Error as ThisError;
2
3///
4/// DurationParseError
5///
6#[derive(Debug, ThisError)]
7pub enum DurationParseError {
8    #[error("invalid duration {value:?}; use positive seconds or a value ending in s, m, h, or d")]
9    Invalid { value: String },
10}
11
12pub fn parse_duration_seconds(value: &str) -> Result<u64, DurationParseError> {
13    let (number, multiplier) = match value.as_bytes().last().copied() {
14        Some(b's') => (&value[..value.len() - 1], 1),
15        Some(b'm') => (&value[..value.len() - 1], 60),
16        Some(b'h') => (&value[..value.len() - 1], 60 * 60),
17        Some(b'd') => (&value[..value.len() - 1], 24 * 60 * 60),
18        Some(b'0'..=b'9') => (value, 1),
19        _ => return invalid_duration(value),
20    };
21    number
22        .parse::<u64>()
23        .ok()
24        .and_then(|amount| amount.checked_mul(multiplier))
25        .filter(|seconds| *seconds > 0)
26        .ok_or_else(|| DurationParseError::Invalid {
27            value: value.to_string(),
28        })
29}
30
31fn invalid_duration(value: &str) -> Result<u64, DurationParseError> {
32    Err(DurationParseError::Invalid {
33        value: value.to_string(),
34    })
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn duration_parser_accepts_units() {
43        assert_eq!(parse_duration_seconds("7d").expect("days"), 604_800);
44        assert_eq!(parse_duration_seconds("2h").expect("hours"), 7_200);
45        assert_eq!(parse_duration_seconds("30m").expect("minutes"), 1_800);
46        assert_eq!(parse_duration_seconds("90s").expect("seconds"), 90);
47        assert_eq!(parse_duration_seconds("42").expect("bare"), 42);
48    }
49
50    #[test]
51    fn duration_parser_rejects_zero_and_unknown_units() {
52        assert!(matches!(
53            parse_duration_seconds("0d"),
54            Err(DurationParseError::Invalid { .. })
55        ));
56        assert!(matches!(
57            parse_duration_seconds("1w"),
58            Err(DurationParseError::Invalid { .. })
59        ));
60    }
61}