use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy)]
pub(super) struct TrackerState {
pub(super) attempts: u32,
pub(super) first_triggered_at: Instant,
}
pub fn parse_duration(s: &str) -> Option<Duration> {
if s.is_empty() {
return None;
}
let bytes = s.as_bytes();
let suffix = *bytes.last()?;
let multiplier_secs: u64 = match suffix {
b's' => 1,
b'm' => 60,
b'h' => 3600,
_ => return None,
};
let digits = &s[..s.len() - 1];
if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
let n: u64 = digits.parse().ok()?;
let total_secs = n.checked_mul(multiplier_secs)?;
Some(Duration::from_secs(total_secs))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_duration_accepts_seconds() {
assert_eq!(parse_duration("1s"), Some(Duration::from_secs(1)));
assert_eq!(parse_duration("10s"), Some(Duration::from_secs(10)));
assert_eq!(parse_duration("300s"), Some(Duration::from_secs(300)));
}
#[test]
fn parse_duration_accepts_minutes() {
assert_eq!(parse_duration("1m"), Some(Duration::from_secs(60)));
assert_eq!(parse_duration("10m"), Some(Duration::from_secs(600)));
}
#[test]
fn parse_duration_accepts_hours() {
assert_eq!(parse_duration("1h"), Some(Duration::from_secs(3600)));
assert_eq!(parse_duration("24h"), Some(Duration::from_secs(24 * 3600)));
}
#[test]
fn parse_duration_accepts_zero() {
assert_eq!(parse_duration("0s"), Some(Duration::ZERO));
assert_eq!(parse_duration("0m"), Some(Duration::ZERO));
assert_eq!(parse_duration("0h"), Some(Duration::ZERO));
}
#[test]
fn parse_duration_rejects_missing_suffix() {
assert_eq!(parse_duration("10"), None);
}
#[test]
fn parse_duration_rejects_empty() {
assert_eq!(parse_duration(""), None);
}
#[test]
fn parse_duration_rejects_non_numeric() {
assert_eq!(parse_duration("fast"), None);
assert_eq!(parse_duration("ten seconds"), None);
assert_eq!(parse_duration("abc"), None);
}
#[test]
fn parse_duration_rejects_compound_form() {
assert_eq!(parse_duration("1m30s"), None);
assert_eq!(parse_duration("1h30m"), None);
assert_eq!(parse_duration("2d"), None);
}
#[test]
fn parse_duration_rejects_negative_and_decimals() {
assert_eq!(parse_duration("-5m"), None);
assert_eq!(parse_duration("1.5h"), None);
assert_eq!(parse_duration("0.5s"), None);
}
#[test]
fn parse_duration_rejects_suffix_only() {
assert_eq!(parse_duration("s"), None);
assert_eq!(parse_duration("m"), None);
assert_eq!(parse_duration("h"), None);
}
#[test]
fn parse_duration_rejects_overflow() {
assert_eq!(parse_duration("99999999999999999999h"), None);
}
}