rust-cli-pomodoro 1.4.6

rust-cli-pomodoro manages your time!
use clap::ArgMatches;
use serde::Deserialize;
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::sync::Arc;

use crate::error::ConfigurationError;
use crate::report::generate_configuration_report;

pub const SLACK_API_URL: &str = "https://slack.com/api/chat.postMessage";

#[derive(Deserialize, Debug, Default, Clone)]
pub struct Configuration {
    #[serde(rename(deserialize = "slack"))]
    slack_configuration: Option<SlackConfiguration>,
    #[serde(rename(deserialize = "discord"))]
    discord_configuration: Option<DiscordConfiguration>,
    work_time_default_value: Option<u16>,
    break_time_default_value: Option<u16>,
}

#[derive(Deserialize, Debug, Default, Clone)]
struct SlackConfiguration {
    token: Option<String>,
    channel: Option<String>,
}

#[derive(Deserialize, Debug, Default, Clone)]
struct DiscordConfiguration {
    webhook_url: Option<String>,
}

impl Configuration {
    pub fn get_slack_token(&self) -> &Option<String> {
        match &self.slack_configuration {
            Some(config) => &config.token,
            None => &None,
        }
    }

    pub fn get_slack_channel(&self) -> &Option<String> {
        match &self.slack_configuration {
            Some(config) => &config.channel,
            None => &None,
        }
    }

    pub fn get_discord_webhook_url(&self) -> &Option<String> {
        match &self.discord_configuration {
            Some(config) => &config.webhook_url,
            None => &None,
        }
    }

    pub fn get_work_time(&self) -> Option<u16> {
        self.work_time_default_value
    }

    pub fn get_break_time(&self) -> Option<u16> {
        self.break_time_default_value
    }
}

pub fn get_configuration(matches: &ArgMatches) -> Result<Arc<Configuration>, ConfigurationError> {
    let configuration_file_path = matches.get_one::<String>("config").map(|s| s.as_str());

    let (configuration, config_error) = load_configuration(configuration_file_path)?;
    let report = generate_configuration_report(&configuration, config_error);
    info!("\nconfig flag result!\n{}", report);

    Ok(Arc::new(configuration))
}

pub fn load_configuration(
    configuration_file: Option<&str>,
) -> Result<(Configuration, Option<ConfigurationError>), ConfigurationError> {
    let (configuration, error) = match configuration_file {
        Some(f) => {
            let path = env::current_dir()
                .map_err(ConfigurationError::LoadFail)?
                .join(f);

            match get_configuration_from_file(path) {
                Ok(config) => (config, None),
                Err(e) => (Configuration::default(), Some(e)),
            }
        }
        None => (Configuration::default(), None),
    };

    debug!("configuration: {:?}", configuration);

    Ok((configuration, error))
}

fn get_configuration_from_file<P: AsRef<Path> + AsRef<OsStr>>(
    path: P,
) -> Result<Configuration, ConfigurationError> {
    if !Path::new(&path).exists() {
        return Err(ConfigurationError::FileNotFound);
    }

    let file = File::open(path).map_err(ConfigurationError::FileOpenError)?;
    let reader = BufReader::new(file);

    let c = serde_json::from_reader(reader).map_err(ConfigurationError::JsonError)?;
    Ok(c)
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use super::load_configuration;

    #[test]
    fn test_initialize_configuration_some() {
        let file = PathBuf::from("resources/test/mock_configuration.json");
        let file = file.to_str();

        let result = load_configuration(file);
        assert_eq!(true, result.is_ok());
        let config = result.unwrap().0;

        let slack_token = config.get_slack_token();
        assert_eq!(true, slack_token.is_some());
        assert!(slack_token.as_ref().unwrap().eq("your-bot-token-string"));

        let slack_channel = config.get_slack_channel();
        assert_eq!(true, slack_channel.is_some());
        assert!(slack_channel.as_ref().unwrap().eq("your-slack-channel-id"));

        let discord_webhook_url = config.get_discord_webhook_url();
        assert_eq!(true, discord_webhook_url.is_some());
        assert!(discord_webhook_url.as_ref().unwrap().eq("your-webhook-url"));

        let work_time = config.get_work_time();
        assert_eq!(true, work_time.is_some());
        assert_eq!(work_time.unwrap(), 30);

        let break_time = config.get_break_time();
        assert_eq!(true, break_time.is_some());
        assert_eq!(break_time.unwrap(), 10);
    }

    #[test]
    fn test_initialize_configuration_none() {
        [PathBuf::from("wrong_path").to_str(), None]
            .into_iter()
            .for_each(|file| {
                let result = load_configuration(file);
                assert_eq!(true, result.is_ok());
                let config = result.unwrap().0;

                let slack_token = config.get_slack_token();
                assert_eq!(true, slack_token.is_none());

                let slack_channel = config.get_slack_channel();
                assert_eq!(true, slack_channel.is_none());

                let discord_webhook_url = config.get_discord_webhook_url();
                assert_eq!(true, discord_webhook_url.is_none());

                let work_time = config.get_work_time();
                assert_eq!(true, work_time.is_none());

                let break_time = config.get_break_time();
                assert_eq!(true, break_time.is_none());
            });
    }
}