diff --git a/src/config.rs b/src/config.rs index bc36419..e81547f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use chrono::Duration; use serde::de::Visitor; -use serde::{Deserialize, Deserializer}; +use serde::{de::Error, Deserialize, Deserializer}; use std::fmt; use std::path::PathBuf; @@ -56,17 +56,34 @@ impl ConfPeriod { } } +/// Deserialize a [SimpleDuration] from a string like "3d" or "24h". fn parse_simple_duration<'de, D>(d: D) -> Result where D: Deserializer<'de>, { - let s = d.deserialize_string(StringVisitor)?; + let s = d.deserialize_str(StrVisitor)?; let s = s.trim(); - let suffix = s.chars().rev().next().unwrap(); + if s.contains(char::is_whitespace) { + return Err(D::Error::custom("duration can't include whitespace")); + } + + let suffix = s + .chars() + .next_back() + .ok_or_else(|| D::Error::custom("duration can't be empty"))?; + + if suffix.is_ascii_digit() { + return Err(D::Error::custom( + r#"specify duration with a suffix, i.e. "24h""#, + )); + } + let value = &s[..s.len() - suffix.len_utf8()]; - let value: u64 = value.parse().expect("failed to parse duration value"); + let value: u64 = value + .parse() + .map_err(|e| D::Error::custom(format!("failed to parse duration value: {e}")))?; let value = value as i64; use SimpleDuration::*; @@ -76,24 +93,23 @@ where 'h' => Hours(value), 'd' => Days(value), 'w' => Weeks(value), - _ => panic!("unknown unit of duration"), + d => return Err(D::Error::custom(format!("unknown unit of duration: {d:?}"))), }) } -struct StringVisitor; +struct StrVisitor; -impl<'de> Visitor<'de> for StringVisitor { - type Value = String; +impl<'de> Visitor<'de> for StrVisitor { + type Value = &'de str; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a string") } - fn visit_string(self, value: String) -> Result { - Ok(value) - } - - fn visit_str(self, value: &str) -> Result { - Ok(value.to_string()) + fn visit_borrowed_str(self, s: &'de str) -> Result + where + E: Error, + { + Ok(s) } }