pash 0.1.1

Simple and easy app for generating and storing passwords
Documentation
//! Module for interacting with `config.toml` and `passwords.toml`.

use crate::files::*;
use crate::generator::Generator;

use serde_derive::Serialize;
use std::collections::HashMap;
use std::io::Read;
use std::io::Write;

/// Struct for serializing passwords' file structure data.
#[derive(Serialize)]
pub struct PasswordsFile(HashMap<String, HashMap<String, String>>);

/// `config.toml` and `passwords.toml` files' content.
pub struct FileContent {
    config_file: fs::File,
    password_file: fs::File,
}

impl FileContent {
    // todo: proper handling of errors, not via 'expect'
    /// Open and store files in a struct.
    pub fn default() -> Self {
        let paths_list = FilePaths::default();
        let config_file = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(paths_list.config_path)
            .expect("could not open");
        let password_file = fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(paths_list.password_path)
            .expect("could not open");
        Self {
            config_file,
            password_file,
        }
    }

    /// Initialize default config if file is empty (we are always reading config from file).
    pub fn init_config(mut self) -> Self {
        let mut config_content: String = String::new();
        self.config_file
            .read_to_string(&mut config_content)
            .expect("failed to make a string");
        if config_content.is_empty() {
            let config_struct: Generator = Generator::new(10)
                .lowercase(true)
                .uppercase(true)
                .symbols(true)
                .numbers(true)
                .begin_with_letter(true)
                .category("unknown".to_string());

            config_content = toml::to_string(&config_struct).unwrap();
            let config_byte_content = config_content.as_bytes();

            self.config_file
                .write_all(config_byte_content)
                .expect("failed to write to file");
        }

        self
    }

    /// Load and edit `passwords.toml` file's content.
    /// *NOTE*: this function does not save content, only returns modified string.
    pub fn edit_passwords(mut self, category_name: Option<&str>, password_name: String) -> String {
        let mut config_content: String = String::new();
        let mut passwords_content: String = String::new();

        self.config_file
            .read_to_string(&mut config_content)
            .expect("failed to make a string");

        self.password_file
            .read_to_string(&mut passwords_content)
            .expect("failed to make a string");

        let generator: Generator =
            toml::from_str(&config_content).expect("failed to generate config");
        let password = generator.generate().unwrap();

        if passwords_content.is_empty() {
            let mut passwords_map: HashMap<String, String> = HashMap::new();
            passwords_map.insert(password_name, password);

            let mut categories_map: HashMap<String, HashMap<String, String>> = HashMap::new();

            if let Some(category_value) = category_name {
                categories_map.insert(category_value.to_string(), passwords_map);
            } else {
                categories_map.insert(generator.category, passwords_map);
            }

            let file_content = PasswordsFile(categories_map);
            toml::to_string_pretty(&file_content).expect("failed to generate string")
        } else {
            let mut file_content: HashMap<String, HashMap<String, String>> =
                toml::from_str(&passwords_content).expect("failed to convert");

            if let Some(category_value) = category_name {
                if let Some(category_map) = file_content.get_mut(category_value) {
                    category_map.insert(password_name, password);
                } else {
                    let mut password_map: HashMap<String, String> = HashMap::new();

                    password_map.insert(password_name, password);
                    file_content.insert(category_value.to_string(), password_map);
                }
            } else if let Some(category_map) = file_content.get_mut(&generator.category) {
                category_map.insert(password_name, password);
            } else {
                let mut password_map: HashMap<String, String> = HashMap::new();

                password_map.insert(password_name, password);
                file_content.insert(generator.category, password_map);
            }

            toml::to_string_pretty(&file_content).expect("failed to generate string")
        }
    }

    /// Save modified content to file with given options (category and password name).
    pub fn save_passwords(self, category_name: Option<&str>, password_name: String) {
        // this might be changed: FilesPaths is dropped right after this line
        let path = { FilePaths::default().password_path };
        let write = self.edit_passwords(category_name, password_name);
        let mut file = fs::OpenOptions::new().write(true).open(path).unwrap();
        file.write_all(write.as_bytes()).unwrap();
    }
}