orcs 0.0.8

Microservices monorepo orchestration tool
Documentation
use serde::{Deserialize, Serialize};
use std::fmt;

/// # Orcs Actions
///
/// This represents a list of actions to run in a stage and for a given
/// type. These can be defined either in a recipe or directly in a service.
///
/// ## Value override
///
/// A recipe can override actions if the actions are either:
///
/// * Missing
/// * an empty string
/// * an empty array
/// * a boolean set to 'true'
///
/// ## TOML Values
///
/// The list of actions can be represented either as a multi-line string, an
/// array of string or a boolean to force-disable the actions.
///
/// ### Multi-line
///
/// ```toml,no_run
/// main = "echo this is
/// echo a multiline
/// echo series of actions"
/// ```
///
/// ### Array
///
/// ```toml,no_run
/// main = [
///   "echo this is",
///   "echo an array",
///   "echo of actions"
/// ]
///
/// ### Boolean
/// ```toml,no_run
/// # This will force-disable the actions and prevent any recipe from
/// # overriding those values.
/// main = false
/// ```
#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum Actions {
    Multiline(String),
    Array(Vec<String>),
    Override(bool),
}

impl Actions {
    pub fn is_empty(&self) -> bool {
        match self {
            Self::Multiline(data) => data.is_empty(),
            Self::Array(data) => data.is_empty(),
            Self::Override(data) => *data,
        }
    }
}

impl fmt::Display for Actions {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::Multiline(actions) => write!(f, "{}", actions),
            Self::Array(actions) => write!(f, "{}", actions.join("\n")),
            Self::Override(true) => write!(f, "true"),
            Self::Override(false) => write!(f, "false"),
        }
    }
}

impl Default for Actions {
    fn default() -> Self {
        Self::Override(true)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rand::distributions::Alphanumeric;
    use rand::prelude::*;

    #[derive(Deserialize)]
    struct DataHolder {
        pub actions: Actions,
    }

    pub fn get_string(mut rng: ThreadRng, len: usize) -> String {
        std::iter::repeat(())
            .map(|()| rng.sample(Alphanumeric))
            .take(len)
            .collect()
    }

    #[test]
    fn load_multiline() {
        let rng = thread_rng();
        let data = format!(
            "{}\n{}\n{}",
            get_string(rng, 10),
            get_string(rng, 10),
            get_string(rng, 10)
        );
        let toml_string = format!("actions = \"\"\"{}\"\"\"", data);

        let actions = toml::from_str::<DataHolder>(toml_string.as_str())
            .unwrap()
            .actions;

        match &actions {
            Actions::Multiline(val) => assert_eq!(val.as_str(), data),
            _ => assert!(false),
        }
        assert_eq!(format!("{}", actions).as_str(), data);
        assert_eq!(actions.is_empty(), false);
    }

    #[test]
    fn load_array() {
        let rng = thread_rng();
        let data = vec![
            get_string(rng, 10),
            get_string(rng, 10),
            get_string(rng, 10),
        ];
        let toml_string = format!(
            "actions = [\"{}\",\"{}\",\"{}\"]",
            data[0], data[1], data[2]
        );
        let output = format!("{}\n{}\n{}", data[0], data[1], data[2]);

        let actions = toml::from_str::<DataHolder>(toml_string.as_str())
            .unwrap()
            .actions;

        match &actions {
            Actions::Array(val) => assert_eq!(val, &data),
            _ => assert!(false),
        }
        assert_eq!(format!("{}", actions).as_str(), output);
        assert_eq!(actions.is_empty(), false);
    }

    #[test]
    fn load_override_false() {
        let data = false;
        let toml_string = "actions = false";
        let output = "false";

        let actions = toml::from_str::<DataHolder>(toml_string).unwrap().actions;

        match &actions {
            Actions::Override(val) => assert_eq!(val, &data),
            _ => assert!(false),
        }
        assert_eq!(format!("{}", actions).as_str(), output);
        assert_eq!(actions.is_empty(), false);
    }

    #[test]
    fn load_override_true() {
        let data = true;
        let toml_string = "actions = true";
        let output = "true";

        let actions = toml::from_str::<DataHolder>(toml_string).unwrap().actions;

        match &actions {
            Actions::Override(val) => assert_eq!(val, &data),
            _ => assert!(false),
        }
        assert_eq!(format!("{}", actions).as_str(), output);
        assert_eq!(actions.is_empty(), true);
    }

    #[test]
    fn empty_multiline() {
        let actions = Actions::Multiline("".to_string());
        assert!(actions.is_empty());
    }

    #[test]
    fn empty_array() {
        let actions = Actions::Array(Vec::new());
        assert!(actions.is_empty());
    }

    #[test]
    fn empty_array2() {
        let actions = Actions::Array(vec!["".to_string(), "".to_string()]);
        assert!(!actions.is_empty());
    }

    #[test]
    fn empty_override() {
        let actions = Actions::Override(true);
        assert!(actions.is_empty());
    }
}