culper-lib 0.1.0

library of common code for culper & culper-server
Documentation
use dirs;
use failure::{Context, Error, Fail, ResultExt};
use std::fs::File;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use toml;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CulperConfig {
    pub targets: Option<Vec<TargetConfig>>,
    pub owners: Option<Vec<UserConfig>>,
    pub admins: Option<Vec<UserConfig>>,
    pub me: UserConfig,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserConfig {
    pub fingerprint: String,
    pub name: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TargetConfig {
    pub id: String,
    pub host: String,
}

#[derive(Debug, Clone)]
pub struct ConfigReader {
    pub path: PathBuf,
    pub config: Option<CulperConfig>,
}

impl ConfigReader {
    pub fn new(raw_config_path: Option<&str>) -> ConfigReader {
        let config_path = match raw_config_path {
            Some(val) => PathBuf::from(val),
            None => get_config_path(),
        };

        ConfigReader {
            path: config_path,
            config: None,
        }
    }

    pub fn read(&mut self) -> Result<CulperConfig, Error> {
        if !&self.path.exists() {
            return Err(format_err!(
                "{} not found. Create one or pass the --config_file option.",
                &self
                    .path
                    .to_str()
                    .expect("Failed converting path to string.")
            ));
        }

        let mut raw_toml = String::new();
        File::open(&self.path)
            .context("Could not open configuration file")?
            .read_to_string(&mut raw_toml)
            .context("Could not read configuration file")?;

        let config = self.read_string_to_config(&raw_toml)?;
        self.config = Some(config.clone());
        Ok(config)
    }

    pub fn add_target(&mut self, host: &str, id: &str) -> Result<(), Error> {
        match &mut self.config {
            Some(ref mut config) => match config.targets {
                None => {
                    config.targets = Some(vec![TargetConfig {
                        host: host.to_owned(),
                        id: id.to_owned(),
                    }]);
                    Ok(())
                }
                Some(ref mut targets) => {
                    targets.push(TargetConfig {
                        host: host.to_owned(),
                        id: id.to_owned(),
                    });

                    Ok(())
                }
            },
            None => Err(format_err!("Config is not set.")),
        }
    }

    pub fn update(&mut self, new_config: CulperConfig) -> &mut Self {
        self.config = Some(new_config);
        self
    }

    pub fn write(&self) -> Result<(), Error> {
        match &self.config {
            Some(config) => {
                OpenOptions::new()
                    .write(true)
                    .create(true)
                    .open(&self.path)?
                    .write_all(toml::to_string(&config)?.as_bytes())?;
                Ok(())
            }
            None => Err(format_err!("No config available to write.")),
        }
    }

    fn read_string_to_config(&self, string: &str) -> Result<CulperConfig, Error> {
        let parsed_toml: CulperConfig = toml::from_str(&string)?;
        Ok(parsed_toml)
    }
}

fn get_config_path() -> PathBuf {
    let mut path = PathBuf::new();
    match dirs::home_dir() {
        Some(home) => path.push(home),
        None => path.push("./"),
    };
    path.push(".culper.toml");
    path
}

pub fn create(name: String, fingerprint: String, config_path: String) -> Result<(), Error> {
    let config = CulperConfig {
        me: UserConfig { name, fingerprint },
        targets: None,
        owners: None,
        admins: None,
    };
    File::create(config_path)?.write_all(toml::to_string(&config)?.as_bytes())?;
    Ok(())
}

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

    #[test]
    fn can_create_config() {
        create(
            "test@test.de".to_owned(),
            "12345678".to_owned(),
            "./culper.toml".to_owned(),
        )
        .unwrap();
        assert!(Path::new("./culper.toml").exists());
    }

    #[test]
    fn can_update_existing_config() {
        let mut config_reader = ConfigReader::new(Some("./culper.toml"));

        config_reader.update(CulperConfig {
            me: UserConfig {
                name: "overwrite@mail.de".to_owned(),
                fingerprint: "1234 5678 ABCD ETC".to_owned(),
            },
            targets: None,
            owners: None,
            admins: None,
        });

        config_reader
            .add_target("www.test.de", "alskjdflsajfd")
            .unwrap();
        config_reader.write().unwrap();

        let mut file = OpenOptions::new().read(true).open("./culper.toml").unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();

        assert_eq!(contents, ::toml::to_string(&config_reader.config).unwrap())
    }
}