use anyhow::{Result, anyhow}; use chrono::{DateTime, Duration, Utc}; pub fn parse_ttl_spec(ttl: &str) -> Result { let ttl = ttl.trim(); if ttl.is_empty() { return Err(anyhow!("ttl must not be empty")); } let (value, multiplier_seconds) = match ttl.chars().last() { Some('s') | Some('S') => (&ttl[..ttl.len() - 1], 1i64), Some('m') | Some('M') => (&ttl[..ttl.len() - 1], 60i64), Some('h') | Some('H') => (&ttl[..ttl.len() - 1], 60i64 * 60), Some('d') | Some('D') => (&ttl[..ttl.len() - 1], 60i64 * 60 * 24), Some('w') | Some('W') => (&ttl[..ttl.len() - 1], 60i64 * 60 * 24 * 7), _ => { return Err(anyhow!( "invalid ttl '{ttl}'. Use a positive duration like 30s, 15m, 1h, 7d, or 2w" )); } }; let value: i64 = value .trim() .parse() .map_err(|_| anyhow!("invalid ttl '{ttl}'. Duration value must be a positive integer"))?; if value <= 0 { return Err(anyhow!("invalid ttl '{ttl}'. Duration value must be greater than zero")); } let total_seconds = value .checked_mul(multiplier_seconds) .ok_or_else(|| anyhow!("invalid ttl '{ttl}'. Duration is too large"))?; Ok(Duration::seconds(total_seconds)) } pub fn expires_at_from_ttl(ttl: Option<&str>) -> Result>> { match ttl { Some(ttl) => { let duration = parse_ttl_spec(ttl)?; Utc::now() .checked_add_signed(duration) .map(Some) .ok_or_else(|| anyhow!("ttl '{ttl}' overflows supported timestamp range")) } None => Ok(None), } }