1
0
mirror of https://github.com/pcvolkmer/checkbar.git synced 2025-04-19 11:06:50 +00:00
checkbar/src/config.rs

245 lines
6.4 KiB
Rust

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(Default, Deserialize)]
pub struct Config {
#[serde(default, deserialize_with = "deserialize_duration")]
pub interval: Option<Duration>,
#[serde(default)]
pub colors: ColorConfig,
#[serde(default)]
pub checks: Vec<CheckConfig>,
}
impl Config {
fn get_config_file() -> String {
match env::args().nth(1) {
Some(config_file) => config_file,
None => format!(
"{}/.checkbar.toml",
dirs::home_dir().unwrap().to_str().unwrap_or("")
),
}
}
pub fn read() -> Self {
Self::read_file(Self::get_config_file().as_str())
}
pub fn read_file(filename: &str) -> Self {
match fs::read_to_string(filename) {
Ok(config) => toml::from_str(config.as_str()).unwrap_or_default(),
Err(_) => Config::default(),
}
}
}
#[derive(Deserialize)]
pub struct ColorConfig {
pub up: String,
pub warn: String,
pub down: String,
}
impl Default for ColorConfig {
fn default() -> Self {
Self {
up: String::from("#00FF00"),
warn: String::from("#FFFF00"),
down: String::from("#FF0000"),
}
}
}
#[derive(Deserialize)]
pub struct CheckConfig {
pub name: String,
pub url: String,
pub check_type: Option<CheckType>,
pub click_cmd: Option<String>,
}
#[derive(Deserialize, PartialEq, Eq)]
pub enum CheckType {
Http,
Actuator,
Tcp,
}
fn deserialize_duration<'de, D>(d: D) -> Result<Option<Duration>, 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")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(format!("{}", v))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
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<Duration> {
let mut duration_in_secs = 0;
if let Ok(re) =
Regex::new(r"^((?P<hours>\d+)h\s*)?((?P<minutes>\d+)m\s*)?((?P<seconds>\d+)s?\s*)?$")
{
if re.is_match(value) {
let parts = re.captures_iter(value).next().unwrap();
if let Some(hours) = parts.name("hours") {
duration_in_secs += hours.as_str().parse::<u64>().unwrap_or(0) * 60 * 60
}
if let Some(minutes) = parts.name("minutes") {
duration_in_secs += minutes.as_str().parse::<u64>().unwrap_or(0) * 60
}
if let Some(seconds) = parts.name("seconds") {
duration_in_secs += seconds.as_str().parse::<u64>().unwrap_or(0)
}
} else {
return None;
}
}
Some(Duration::from_secs(duration_in_secs))
}
#[cfg(test)]
mod tests {
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_config_without_checks() {
let config: Config = toml::from_str(
r#"
interval = "2m 3s"
"#,
)
.unwrap();
assert_eq!(config.checks.len(), 0);
}
#[test]
fn test_should_parse_config_with_default_colors() {
let config: Config = toml::from_str(
r#"
interval = "2m 3s"
"#,
)
.unwrap();
assert_eq!(config.colors.up, "#00FF00".to_string());
assert_eq!(config.colors.warn, "#FFFF00".to_string());
assert_eq!(config.colors.down, "#FF0000".to_string());
}
#[test]
fn test_should_read_and_parse_file() {
let config = Config::read_file("./tests/testconfig1.toml");
assert_eq!(config.interval, Some(Duration::from_secs(10)));
assert_eq!(config.checks.len(), 1);
assert_eq!(config.checks[0].name, "www");
assert_eq!(config.checks[0].url, "https://example.com");
}
#[test]
fn test_should_return_default_if_no_config_file() {
let config = Config::read_file("./tests/no_testconfig.toml");
assert_eq!(config.interval, None);
assert_eq!(config.checks.len(), 0);
}
#[test]
fn test_should_parse_durations() {
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("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("invalid"), None);
assert_eq!(parse_duration("1x30m10q"), None);
}
}