tusk_data 0.0.1

Data types for monitoring with Tusk
Documentation
use regex::{Regex, RegexBuilder};

use std::collections::BTreeMap;

pub type Tags = Vec<String>;

pub type Labels = BTreeMap<String, String>;

#[derive(Serialize, Deserialize, Clone, Debug, Ord, PartialEq, PartialOrd, Eq)]
#[serde(default)]
pub struct MetaData {
    pub labels: Labels,
    pub tags: Tags,
}

impl Default for MetaData {
	/// Returns a default implementation of the `MetaData`
	/// ```
	/// let m = tusk_data::metadata::MetaData::default();
	/// ```
    fn default() -> Self {
        MetaData {
            labels: Labels::new(),
            tags: Tags::new(),
        }
    }
}

impl MetaData {
	/// Returns a new instance of `MetaData`
	/// ```
	/// let m = tusk_data::metadata::MetaData::new();
	/// ```
    pub fn new() -> Self {
        Self::default()
    }

    /// Adds a new tag to the `MetaData`
    /// ```
    /// assert_eq!(tusk_data::metadata::MetaData::new()
    /// 			.add_tag("example")
    /// 			.add_tag("another_tag")
    /// 			.tags, 
    /// 		vec!("example".to_string(), "another_tag".to_string())
    /// 	);
    /// ```
    pub fn add_tag(&mut self, tag: &str) -> &mut Self {
        self.tags.push(tag.to_string());
        self
    }

    /// Returns all the `Tags` for this piece of `MetaData`
    /// ```
    /// assert_eq!(tusk_data::metadata::MetaData::new()
    /// 			.add_tag("example")
    /// 			.add_tag("another_tag")
    /// 			.get_tags(), 
    /// 		vec!("example".to_string(), "another_tag".to_string())
    /// 	);
    /// ```
    pub fn get_tags(&self) -> Tags {
        self.tags.clone()
    }

    /// Adds a new label to this piece of `MetaData`
    /// ```
    /// let mut expect = tusk_data::metadata::Labels::new();
    /// expect.insert("example".to_string(), "value".to_string());
    /// assert_eq!(tusk_data::metadata::MetaData::new()
    /// 			.add_label("example", "value")
    /// 			.labels, expect);
    /// ```
    pub fn add_label(&mut self, name: &str, value: &str) -> &mut Self {
        self.labels.insert(name.to_string(), value.to_string());
        self
    }

    /// Returns all `Labels` for this piece of `MetaData`
    /// ```
    /// let mut expect = tusk_data::metadata::Labels::new();
    /// expect.insert("example".to_string(), "value".to_string());
    /// assert_eq!(tusk_data::metadata::MetaData::new()
    /// 			.add_label("example", "value")
    /// 			.get_labels(), expect);
    /// ```
    pub fn get_labels(&self) -> Labels {
        self.labels.clone()
    }

    /// Returns true if the labels of the `MetaData` on the LHS are a
    /// superset of the labels of the `MetaData` on the RHS.
    /// ```
    /// let mut base = tusk_data::metadata::MetaData::new();
    /// base.add_label("example", "value");
    /// let mut cover = tusk_data::metadata::MetaData::new();
    /// cover.add_label("example", "value");
    /// cover.add_label("example2", "value2");
    /// assert!(cover.superset(&base));
    /// assert!(!base.superset(&cover));
    /// // If the two are equal then they are supersets of each other
    /// assert!(base.superset(&base));
    /// ```
    pub fn superset(&self, md: &MetaData) -> bool {
        for (key, _) in &md.labels {
            if !self.labels.contains_key(key) {
                return false;
            }
        }
        true
    }

    /// Compiles the regexes so that the `MetaData` into a `MetaDataFilter`
    /// can be matched against other `MetaData`. Any failures
    /// are simply skipped over and not added to the
    /// filter.
    /// ```
    ///	assert_eq!(tusk_data::metadata::MetaData::new()
    /// 	.add_label("test", "value")
    /// 	.add_tag("example")
    ///		.compile()
    ///		.labels
    ///		.len(), 1);
    ///	assert_eq!(tusk_data::metadata::MetaData::new()
    /// 	.add_label("test", "value")
    /// 	.add_tag("bad_tag")
    ///		.compile()
    ///		.tags
    ///		.len(), 1);
    /// ```
    pub fn compile(&self) -> MetaDataFilter {
        MetaDataFilter {
            tags: self.tags
                .iter()
                .filter_map(|tag| match RegexBuilder::new(tag).build() {
                    Err(error) => {
                        warn!("{}: Could not build regex {:?}", error, tag);
                        None
                    }
                    Ok(regex) => {
                        trace!("Build regex {:?}", tag);
                        Some(regex)
                    }
                })
                .collect(),
            labels: self.labels
                .iter()
                .filter_map(|(key, value)| match RegexBuilder::new(value).build() {
                    Err(error) => {
                        warn!(
                            "{}: Could not build regex {:?} for key {:?}",
                            error, value, key
                        );
                        None
                    }
                    Ok(regex) => {
                        trace!("Build regex {:?} for key {:?}", value, key);
                        Some((key.clone(), regex))
                    }
                })
                .collect(),
        }
    }
}

pub struct MetaDataFilter {
    pub tags: Vec<Regex>,
    pub labels: BTreeMap<String, Regex>,
}

impl MetaDataFilter {
    /// Performs checks to see if the MetaDataFilter is_match
    /// the provided MetaData document.
    /// ```
    ///	assert!(tusk_data::metadata::MetaData::new()
    /// 	.add_label("test", "value")
    /// 	.add_tag("example")
    ///		.compile()
    ///		.is_match(tusk_data::metadata::MetaData::new()
    ///			.add_tag("example")
    ///			.add_label("test", "value")
    ///		));
    ///	assert!(!tusk_data::metadata::MetaData::new()
    /// 	.add_label("test", "value")
    /// 	.add_tag("bad_tag")
    ///		.compile()
    ///		.is_match(tusk_data::metadata::MetaData::new()
    ///			.add_tag("example")
    ///			.add_label("test", "value")
    ///		));
    pub fn is_match(&self, md: &MetaData) -> bool {
        // Check the labels
        for (key, labels_re) in &self.labels {
            for (label_name, label_value) in &md.get_labels() {
                if label_name == key {
                    if !labels_re.is_match(&label_value) {
                        return false;
                    }
                } else {
                    // The key is not defined
                    return false;
                }
            }
        }
        // Check the tags
        for tag_re in &self.tags {
            for tag in md.get_tags() {
                if !tag_re.is_match(&tag) {
                    return false;
                }
            }
        }
        true
    }
}

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

    //#[test]
    fn test_metadata_builder() {
        let mut md = MetaData::new();
        md.add_tag("test")
            .add_tag("another_test")
            .add_label("example", "value");
        // Check that we can add labels OK.
        assert_eq!(
            md.get_labels().get("example").unwrap(),
            &"value".to_string()
        );
        // Check that we can add tags OK.
        assert_eq!(md.get_tags(), vec!["test", "another_test"]);
    }

    //#[test]
    fn test_metadata_filter() {
        let mut md = MetaData::new();
        md.add_tag("test")
            .add_tag("another_test")
            .add_label("example", "value");
        // Check for a single tag match
        assert!(
            MetaData::new()
                .add_tag("test")
                .compile()
                .is_match(&md)
        );
        // Check for a tag failure
        assert!(!MetaData::new()
            .add_tag("test")
            .add_tag("doot doot")
            .compile()
            .is_match(&md));
        // Check for tag match and label match
        assert!(
            MetaData::new()
                .add_tag("test")
                .add_label("example", "^val.e")
                .compile()
                .is_match(&md)
        );
        // Check for tag match and label failure
        assert!(!MetaData::new()
            .add_tag("test")
            .add_label("example", "^$")
            .compile()
            .is_match(&md));
        // Check for tag failure and label match
        assert!(!MetaData::new()
            .add_tag("another_test")
            .add_label("example", ".*")
            .compile()
            .is_match(&md));
        // Check for label key failure
        assert!(!MetaData::new()
            .add_label("key not exist", "^.")
            .compile()
            .is_match(&md));
    }
}