view src/rule.rs @ 26:071e6d446dbd draft

Move tests for rule into right module
author Lewin Bormann <lbo@spheniscida.de>
date Sun, 04 Dec 2016 17:47:36 +0100
parents 2b5e442c8bbb
children
line wrap: on
line source

#![allow(dead_code)]

use std::default::Default;

use toml;

use config::tomlerr;
use priority;

#[derive(Clone, Debug)]
pub enum FacPattern {
    /// '*'
    Wildcard,
    Facility(priority::Facility),
    MultiFacility(Vec<priority::Facility>),
}

#[derive(Clone, Debug)]
pub enum LvlPattern {
    Wildcard,
    /// Matches that level, or any lower (more important) level
    Level(priority::Level),
    /// Matches only that level
    ExactLevel(priority::Level),
    MultiLevel(Vec<priority::Level>),
}

impl Default for FacPattern {
    fn default() -> FacPattern {
        FacPattern::Wildcard
    }
}

impl Default for LvlPattern {
    fn default() -> LvlPattern {
        LvlPattern::Wildcard
    }
}

/// A Filter specifies one or more Facilities, and one or more Levels.
type Filter = (FacPattern, LvlPattern);
/// A Matcher is one or more Filters.
type Matcher = Vec<Filter>;

/// a pattern for a rule matches syslog messages based on level and facility. Valid patterns
/// are:
///
/// *.* -- match all
/// mail.* -- match all mail records
/// mail,news.* -- match all mail and news records
/// mail.warn,info -- match all mail records at warn or info
/// mail,news.info,warn -- match all mail or news records at warn or info.
/// mail.*;news,auth.info -- match all mail records and records for news or auth at info or higher
/// (!) => if only a single level is given, this means "this level or more important"; if you only want to
/// match that single level, use a rule like "mail.=info".
pub fn parse_matcher(s: &str) -> Result<Matcher, toml::Error> {
    let mut matcher = Vec::with_capacity(3);

    for filter in s.trim().split(';') {
        if filter.len() == 0 {
            return Ok(matcher);
        }

        match parse_filter(filter) {
            Ok(f) => matcher.push(f),
            Err(e) => return Err(e),
        }
    }
    Ok(matcher)
}

fn parse_filter(s: &str) -> Result<Filter, toml::Error> {
    let mut parts = s.trim().split('.');

    if let Some(facilities) = parts.next() {
        if let Some(levels) = parts.next() {
            return match (parse_facility_pattern(facilities), parse_level_pattern(levels)) {
                (Ok(f), Ok(l)) => Ok((f, l)),
                (Err(e), Err(f)) => tomlerr(format!("{} {}", e, f)),
                (Ok(_), Err(e)) => tomlerr(format!("{}", e)),
                (Err(e), Ok(_)) => tomlerr(format!("{}", e)),
            };
        }
    }
    tomlerr(format!("Bad filter pattern: {}", s))
}

fn parse_facility_pattern(s: &str) -> Result<FacPattern, toml::Error> {
    let list: Vec<String> = s.trim().split(',').map(String::from).collect();

    if list.len() > 1 {
        let mut facilities = Vec::with_capacity(list.len());

        for f in list {
            if let Some(fac) = priority::str_to_facility(f.trim()) {
                facilities.push(fac);
            } else {
                return tomlerr(format!("Unrecognized facility: {}", f));
            }
        }
        Ok(FacPattern::MultiFacility(facilities))
    } else if list.len() == 1 {
        let l = &list[0];

        if l == "*" {
            Ok(FacPattern::Wildcard)
        } else if let Some(fac) = priority::str_to_facility(l) {
            Ok(FacPattern::Facility(fac))
        } else {
            tomlerr(format!("Unknown facility: {}", l))
        }
    } else {
        return tomlerr(format!("Level pattern can't be empty"));
    }
}

fn parse_level_pattern(s: &str) -> Result<LvlPattern, toml::Error> {
    let list: Vec<String> = s.trim().split(',').map(String::from).collect();

    if list.len() > 1 {
        let mut levels = Vec::with_capacity(list.len());

        for l in list {
            if let Some(lvl) = priority::str_to_level(l.trim()) {
                levels.push(lvl);
            } else {
                return tomlerr(format!("Unrecognized facility: {}", l));
            }
        }
        Ok(LvlPattern::MultiLevel(levels))
    } else if list.len() == 1 {
        let l = &list[0];

        // exact rule
        if &l[0..1] == "=" {
            if let Some(fac) = priority::str_to_level(&l[1..]) {
                Ok(LvlPattern::ExactLevel(fac))
            } else {
                tomlerr(format!("Unknown exact facility: {}", l))
            }
        } else if &l[..] == "*" {
            Ok(LvlPattern::Wildcard)
        } else if let Some(lvl) = priority::str_to_level(l) {
            Ok(LvlPattern::Level(lvl))
        } else {
            tomlerr(format!("Unknown facility: {}", &list[0]))
        }
    } else {
        return tomlerr(format!("Level pattern can't be empty"));
    }
}

#[derive(Clone, Debug, Default)]
pub struct Rule {
    pattern: Matcher,
    // refers to the file entry name, not the file name.
    file: String,
    remote_dest: String,
    stop: bool,
}

pub fn new_rule(pattern: Matcher, file: String, remote_dest: String, stop: bool) -> Rule {
    Rule {
        pattern: pattern,
        file: file,
        remote_dest: remote_dest,
        stop: stop
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_config_matcher_parse() {
        let m = "mail,news.info,warn;kern.=warn;syslog.warn;daemon.debug,info,warn";
        assert_eq!(format!("{:?}", super::parse_matcher(m)),
                   "Ok([(MultiFacility([NEWS, LOCAL0]), MultiLevel([WARNING, NONE])), \
                    (Facility(KERN), ExactLevel(WARNING)), (Facility(SYSLOG), Level(WARNING)), \
                    (Facility(DAEMON), MultiLevel([WARNING, NONE, NONE]))])");

        let m = "mail, news.info; kern.warn";

        assert_eq!(format!("{:?}", super::parse_matcher(m)),
                   "Ok([(MultiFacility([NEWS, LOCAL0]), Level(INFO)), (Facility(KERN), \
                    Level(WARNING))])");

        assert_eq!(format!("{:?}", super::parse_matcher("")), "Ok([])");
    }
}