canic_host/duration/
mod.rs1use thiserror::Error as ThisError;
2
3#[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}