diff --git a/README.adoc b/README.adoc index a41992f..b785ac6 100644 --- a/README.adoc +++ b/README.adoc @@ -35,7 +35,12 @@ The value for `interval` can be set by using plain seconds or using units of `h` default to 60 seconds. ---- -# Update interval using value with units. Default value if not set is 60 sec. +# Update interval using seconds as number. +interval = 60 + +# or ... + +# Update interval using value with units. interval = "2m 30s" ---- diff --git a/src/config.rs b/src/config.rs index 0e7831b..5f8eb1b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,15 @@ -use regex::Regex; -use serde::Deserialize; +use std::fmt::Formatter; use std::time::Duration; use std::{env, fs}; +use regex::Regex; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer}; + #[derive(Deserialize)] pub struct Config { - pub interval: Option, + #[serde(default, deserialize_with = "deserialize_duration")] + pub interval: Option, pub colors: Option, pub checks: Vec, } @@ -60,15 +64,42 @@ pub fn get_config() -> Config { } } -pub fn parse_duration(value: Option) -> Duration { - let mut result = 0; - let value = match &value { - Some(value) => { - let result = value; - result.as_str() +fn deserialize_duration<'de, D>(d: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct StringVisitor; + + impl<'de> Visitor<'de> for StringVisitor { + type Value = String; + + fn expecting(&self, f: &mut Formatter) -> std::fmt::Result { + f.write_str("a number or string with parsable duration") } - _ => return Duration::from_secs(60), - }; + + fn visit_i64(self, v: i64) -> Result + where + E: Error, + { + Ok(format!("{}", v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + Ok(v.to_string()) + } + } + + match d.deserialize_string(StringVisitor) { + Ok(value) => Ok(parse_duration(value.as_str())), + Err(err) => Err(err), + } +} + +fn parse_duration(value: &str) -> Option { + let mut result = 0; if let Ok(re) = Regex::new(r"^((?P\d+)h\s*)?((?P\d+)m\s*)?((?P\d+)s?\s*)?$") { @@ -93,59 +124,81 @@ pub fn parse_duration(value: Option) -> Duration { }; } } else { - return Duration::from_secs(60); + return None; } } - Duration::from_secs(result) + Some(Duration::from_secs(result)) } #[cfg(test)] mod tests { - use crate::config::parse_duration; use std::time::Duration; + use crate::config::{parse_duration, Config}; + + #[test] + fn test_should_parse_config_with_number_interval() { + let config: Config = toml::from_str( + r#" + interval = 123 + + [[checks]] + name = "example" + url = "https://example.com" + "#, + ) + .unwrap(); + + assert_eq!(config.interval, Some(Duration::from_secs(123))); + } + + #[test] + fn test_should_parse_config_with_parsed_interval() { + let config: Config = toml::from_str( + r#" + interval = "2m 3s" + + [[checks]] + name = "example" + url = "https://example.com" + "#, + ) + .unwrap(); + + assert_eq!(config.interval, Some(Duration::from_secs(123))); + } + + #[test] + fn test_should_parse_config_without_interval() { + let config: Config = toml::from_str( + r#" + [[checks]] + name = "example" + url = "https://example.com" + "#, + ) + .unwrap(); + + assert_eq!(config.interval, None); + } + #[test] fn test_should_parse_durations() { - assert_eq!( - parse_duration(Some("1m30s".to_string())), - Duration::from_secs(90) - ); - assert_eq!( - parse_duration(Some("2m".to_string())), - Duration::from_secs(120) - ); - assert_eq!( - parse_duration(Some("1h1m1s".to_string())), - Duration::from_secs(3661) - ); - assert_eq!( - parse_duration(Some("90".to_string())), - Duration::from_secs(90) - ); + assert_eq!(parse_duration("1m30s"), Some(Duration::from_secs(90))); + assert_eq!(parse_duration("2m"), Some(Duration::from_secs(120))); + assert_eq!(parse_duration("1h1m1s"), Some(Duration::from_secs(3661))); + assert_eq!(parse_duration("90"), Some(Duration::from_secs(90))); } #[test] fn test_should_parse_durations_with_whitespaces() { - assert_eq!( - parse_duration(Some("1m 30s".to_string())), - Duration::from_secs(90) - ); - assert_eq!( - parse_duration(Some("1h 1m 1s".to_string())), - Duration::from_secs(3661) - ); + assert_eq!(parse_duration("1m 30s"), Some(Duration::from_secs(90))); + assert_eq!(parse_duration("1h 1m 1s"), Some(Duration::from_secs(3661))); } #[test] fn test_should_return_default_for_unparseable_durations() { - assert_eq!(parse_duration(None), Duration::from_secs(60)); - assert_eq!( - parse_duration(Some("invalid".to_string())), - Duration::from_secs(60) - ); - assert_eq!( - parse_duration(Some("1x30m10q".to_string())), - Duration::from_secs(60) - ); + assert_eq!(parse_duration("invalid"), None); + assert_eq!(parse_duration("1x30m10q"), None); } } diff --git a/src/main.rs b/src/main.rs index 180c70f..16757e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,15 @@ -mod checker; -mod config; - use std::process; +use std::time::Duration; use serde::Deserialize; use serde_json::json; use tokio::task; use crate::checker::{ActuatorChecker, CheckResult, HttpChecker, TcpChecker}; -use crate::config::{get_config, parse_duration, CheckConfig, CheckType}; +use crate::config::{get_config, CheckConfig, CheckType}; + +mod checker; +mod config; #[derive(Deserialize)] struct ClickEvent { @@ -101,7 +102,11 @@ async fn main() { loop { let config = get_config(); print_states(&config.checks).await; - std::thread::sleep(parse_duration(config.interval)); + let interval = match config.interval { + Some(value) => value, + _ => Duration::from_secs(60), + }; + std::thread::sleep(interval); } });