minmon 0.13.0

An opinionated minimal monitoring and alarming tool
Documentation
use std::str::FromStr;

use crate::action;
use crate::config;
use crate::ActionMap;
use crate::{Error, PlaceholderMap, Result};

pub struct Report {
    pub when: ReportWhen,
    placeholders: PlaceholderMap,
    events: Vec<Event>,
}

#[derive(Clone)]
pub enum ReportWhen {
    Interval(std::time::Duration),
    Cron(cron::Schedule),
}

impl Report {
    fn new(
        when: &config::ReportWhen,
        placeholders: PlaceholderMap,
        events: Vec<Event>,
    ) -> Result<Self> {
        if when.interval.is_some() && when.cron.is_some() {
            Err(Error(String::from(
                "'interval' and 'cron' cannot be set both.",
            )))
        } else if let Some(0) = when.interval {
            Err(Error(String::from("'interval' cannot be 0.")))
        } else {
            let when = if let Some(interval) = when.interval {
                ReportWhen::Interval(std::time::Duration::from_secs(interval.into()))
            } else if let Some(cron) = when.cron.as_ref() {
                let schedule = cron::Schedule::from_str(cron).map_err(|x| Error(x.to_string()))?;
                ReportWhen::Cron(schedule)
            } else {
                ReportWhen::Interval(std::time::Duration::from_secs(
                    config::default::report_interval().into(),
                ))
            };
            Ok(Self {
                when,
                placeholders,
                events,
            })
        }
    }

    pub async fn trigger(&mut self) {
        let mut placeholders = crate::global_placeholders();
        crate::merge_placeholders(&mut placeholders, &self.placeholders);
        for event in self.events.iter_mut() {
            let result = event.trigger(placeholders.clone()).await;
            if let Err(err) = result {
                log::error!("Error in report event '{}': {}", event.name, err);
            }
        }
    }
}

struct Event {
    name: String,
    placeholders: PlaceholderMap,
    action: std::sync::Arc<dyn action::Action>,
}

impl Event {
    fn new(
        name: String,
        placeholders: PlaceholderMap,
        action: std::sync::Arc<dyn action::Action>,
    ) -> Result<Self> {
        if name.is_empty() {
            Err(Error(String::from("'name' cannot be empty.")))
        } else {
            Ok(Self {
                name,
                placeholders,
                action,
            })
        }
    }

    fn add_placeholders(&self, placeholders: &mut PlaceholderMap) {
        placeholders.insert(String::from("event_name"), self.name.clone());
        crate::merge_placeholders(placeholders, &self.placeholders);
    }

    async fn trigger(&self, mut placeholders: PlaceholderMap) -> Result<()> {
        self.add_placeholders(&mut placeholders);
        self.action.trigger(placeholders).await
    }
}

pub fn from_report_config(report_config: &config::Report, actions: &ActionMap) -> Result<Report> {
    let mut events: Vec<Event> = Vec::new();
    let mut used_names = std::collections::HashSet::new();
    for event_config in report_config.events.iter() {
        if !used_names.insert(event_config.name.clone()) {
            return Err(Error(format!(
                "Found duplicate event name: {}",
                event_config.name
            )));
        }
        let event = Event::new(
            event_config.name.clone(),
            event_config.placeholders.clone(),
            action::get_action(&event_config.action, actions)?,
        )?;
        events.push(event);
    }
    Report::new(
        &report_config.when,
        report_config.placeholders.clone(),
        events,
    )
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_report_validation() {
        let invalid_interval = config::ReportWhen {
            interval: Some(0),
            ..Default::default()
        };
        assert!(matches!(
            Report::new(&invalid_interval, PlaceholderMap::new(), Vec::new()),
            Err(Error(_))
        ));
    }
}