From 20fbb622a180064a1857307255f87fced4fc9fbd Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Tue, 10 Sep 2019 22:53:45 +0200 Subject: [PATCH] Add config_set tests --- Cargo.lock | 98 ++++++ Cargo.toml | 3 + src/config/mod.rs | 417 +++++++++++++++++++++---- src/main.rs | 8 +- src/res/test/config_with_bad_yaml.yaml | 12 + src/res/test/working_config.yaml | 10 + 6 files changed, 477 insertions(+), 71 deletions(-) create mode 100644 src/res/test/config_with_bad_yaml.yaml create mode 100644 src/res/test/working_config.yaml diff --git a/Cargo.lock b/Cargo.lock index 5a3190e..800cedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,15 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "c2-chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cc" version = "1.0.41" @@ -224,6 +233,7 @@ dependencies = [ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "zip 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -263,6 +273,16 @@ name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "getrandom" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -326,6 +346,11 @@ name = "podio" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ppv-lite86" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro2" version = "0.4.30" @@ -358,6 +383,27 @@ dependencies = [ "proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -371,6 +417,22 @@ name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_os" version = "0.1.3" @@ -424,6 +486,14 @@ name = "regex-syntax" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rust-argon2" version = "0.5.1" @@ -514,6 +584,19 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "term" version = "0.6.1" @@ -569,6 +652,11 @@ name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "widestring" version = "0.4.0" @@ -629,6 +717,7 @@ dependencies = [ "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" "checksum bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6584aa36f5ad4c9247f5323b0a42f37802b37a836f0ad87084d7a33961abe25f" +"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" "checksum cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" @@ -645,6 +734,7 @@ dependencies = [ "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaffba6388640136149e18ed080b77a78611c1e1d6de75aedcdf78df5d4682" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" @@ -655,18 +745,24 @@ dependencies = [ "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd" +"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "175a40b9cf564ce9bf050654633dbf339978706b8ead1a907bb970b63185dd95" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" +"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" @@ -677,6 +773,7 @@ dependencies = [ "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" @@ -685,6 +782,7 @@ dependencies = [ "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" "checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index 1ff01ea..4c772b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,8 @@ log = "0.4.8" simplelog = "0.7.1" zip = "0.5.3" +[dev-dependencies] +tempfile = "3.1.0" + [build-dependencies] cmake = "0.1.31" \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs index 93b7dce..bd178ce 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,7 +1,7 @@ extern crate dirs; -use std::path::Path; -use std::fs; +use std::path::{Path, PathBuf}; +use std::{fs, io}; use crate::matcher::Match; use std::fs::{File, create_dir_all}; use std::io::Read; @@ -11,6 +11,8 @@ use crate::system::SystemManager; use std::collections::HashSet; use std::process::exit; use log::{debug, info, warn, error}; +use std::fmt; +use std::error::Error; pub(crate) mod runtime; @@ -71,8 +73,10 @@ pub struct Configs { macro_rules! validate_field { ($result:expr, $field:expr, $def_value:expr) => { if $field != $def_value { - let field_name = stringify!($field); - let field_name = &field_name[5..]; // Remove the 'self.' prefix + let mut field_name = stringify!($field); + if field_name.starts_with("self.") { + field_name = &field_name[5..]; // Remove the 'self.' prefix + } error!("Validation error, parameter '{}' is reserved and can be only used in the default.yaml config file", field_name); $result = false; } @@ -85,7 +89,7 @@ impl Configs { * It makes sure that app-specific config instances do not define * attributes reserved to the default config. */ - fn validate_config(&self) -> bool { + fn validate_specific_config(&self) -> bool { let mut result = true; validate_field!(result, self.config_caching_interval, default_config_caching_interval()); @@ -109,24 +113,26 @@ impl Default for BackendType { } impl Configs { - fn load_config(path: &Path) -> Configs { + fn load_config(path: &Path) -> Result { let file_res = File::open(path); if let Ok(mut file) = file_res { let mut contents = String::new(); - file.read_to_string(&mut contents) - .expect("Unable to read config file"); + let res = file.read_to_string(&mut contents); + + if let Err(_) = res { + return Err(ConfigLoadError::UnableToReadFile) + } let config_res = serde_yaml::from_str(&contents); match config_res { - Ok(config) => config, + Ok(config) => Ok(config), Err(e) => { - error!("Error parsing YAML file {}, invalid syntax: {}", path.to_str().unwrap_or(""), e); - exit(2); + Err(ConfigLoadError::InvalidYAML(path.to_owned(), e.to_string())) } } }else{ - panic!("Config file not found...") + Err(ConfigLoadError::FileNotFound) } } } @@ -137,62 +143,62 @@ pub struct ConfigSet { specific: Vec, } -impl ConfigSet { // TODO: tests -pub fn load(dir_path: &Path) -> ConfigSet { - if !dir_path.is_dir() { - error!("Invalid config directory"); - exit(2); - } - - let default_file = dir_path.join(DEFAULT_CONFIG_FILE_NAME); - let default = Configs::load_config(default_file.as_path()); - - let mut specific = Vec::new(); - - // Used to make sure no duplicates are present - let mut name_set = HashSet::new(); - - for entry in fs::read_dir(dir_path) - .expect("Cannot read espanso config directory!") { - - let entry = entry; - if let Ok(entry) = entry { - let path = entry.path(); - - // Skip the default one, already loaded - if path.file_name().unwrap_or("".as_ref()) == "default.yaml" { - continue; - } - - let config = Configs::load_config(path.as_path()); - - if !config.validate_config() { - error!("Error while parsing {}, please remove reserved parameters", path.to_str().unwrap_or("")); - exit(3); - } - - if config.name == "default" { - error!("Error while parsing {}, please specify a 'name' field", path.to_str().unwrap_or("")); - exit(4); - } - - if name_set.contains(&config.name) { - error!("Error while parsing {} : the specified name is already used, please use another one", path.to_str().unwrap_or("")); - exit(5); - } - - name_set.insert(config.name.clone()); - specific.push(config); +impl ConfigSet { + pub fn load(dir_path: &Path) -> Result { + if !dir_path.is_dir() { + return Err(ConfigLoadError::InvalidConfigDirectory) } + + let default_file = dir_path.join(DEFAULT_CONFIG_FILE_NAME); + let default = Configs::load_config(default_file.as_path())?; + + let mut specific = Vec::new(); + + // Used to make sure no duplicates are present + let mut name_set = HashSet::new(); + + let dir_entry = fs::read_dir(dir_path); + if dir_entry.is_err() { + return Err(ConfigLoadError::UnableToReadFile) + } + let dir_entry = dir_entry.unwrap(); + + for entry in dir_entry { + let entry = entry; + if let Ok(entry) = entry { + let path = entry.path(); + + // Skip the default one, already loaded + if path.file_name().unwrap_or("".as_ref()) == "default.yaml" { + continue; + } + + let config = Configs::load_config(path.as_path())?; + + if !config.validate_specific_config() { + return Err(ConfigLoadError::InvalidParameter(path.to_owned())) + } + + if config.name == "default" { + return Err(ConfigLoadError::MissingName(path.to_owned())); + } + + if name_set.contains(&config.name) { + return Err(ConfigLoadError::NameDuplicate(path.to_owned())); + } + + name_set.insert(config.name.clone()); + specific.push(config); + } + } + + Ok(ConfigSet { + default, + specific + }) } - ConfigSet { - default, - specific - } -} - - pub fn load_default() -> ConfigSet { + pub fn load_default() -> Result { let res = dirs::home_dir(); if let Some(home_dir) = res { let espanso_dir = home_dir.join(".espanso"); @@ -205,16 +211,17 @@ pub fn load(dir_path: &Path) -> ConfigSet { // If config file does not exist, create one from template if !default_file.exists() { - fs::write(&default_file, DEFAULT_CONFIG_FILE_CONTENT) - .expect("Unable to write default config file"); + let result = fs::write(&default_file, DEFAULT_CONFIG_FILE_CONTENT); + if result.is_err() { + return Err(ConfigLoadError::UnableToCreateDefaultConfig) + } } return ConfigSet::load(espanso_dir.as_path()) } } - error!("Could not generate default position for config file"); - exit(1); + return Err(ConfigLoadError::UnableToCreateDefaultConfig) } } @@ -222,4 +229,276 @@ pub trait ConfigManager<'a> { fn active_config(&'a self) -> &'a Configs; fn default_config(&'a self) -> &'a Configs; fn matches(&'a self) -> &'a Vec; +} + +// Error handling +#[derive(Debug, PartialEq)] +pub enum ConfigLoadError { + FileNotFound, + UnableToReadFile, + InvalidYAML(PathBuf, String), + InvalidConfigDirectory, + InvalidParameter(PathBuf), + MissingName(PathBuf), + NameDuplicate(PathBuf), + UnableToCreateDefaultConfig, +} + +impl fmt::Display for ConfigLoadError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ConfigLoadError::FileNotFound => write!(f, "File not found"), + ConfigLoadError::UnableToReadFile => write!(f, "Unable to read config file"), + ConfigLoadError::InvalidYAML(path, e) => write!(f, "Error parsing YAML file '{}', invalid syntax: {}", path.to_str().unwrap_or_default(), e), + ConfigLoadError::InvalidConfigDirectory => write!(f, "Invalid config directory"), + ConfigLoadError::InvalidParameter(path) => write!(f, "Invalid parameter in '{}', use of reserved parameters in app-specific configs is not permitted", path.to_str().unwrap_or_default()), + ConfigLoadError::MissingName(path) => write!(f, "The 'name' field is required in app-specific configurations, but it's missing in '{}'", path.to_str().unwrap_or_default()), + ConfigLoadError::NameDuplicate(path) => write!(f, "Found duplicate 'name' in '{}', please use different names", path.to_str().unwrap_or_default()), + ConfigLoadError::UnableToCreateDefaultConfig => write!(f, "Could not generate default config file"), + } + } +} + +impl Error for ConfigLoadError { + fn description(&self) -> &str { + match self { + ConfigLoadError::FileNotFound => "File not found", + ConfigLoadError::UnableToReadFile => "Unable to read config file", + ConfigLoadError::InvalidYAML(_, _) => "Error parsing YAML file, invalid syntax", + ConfigLoadError::InvalidConfigDirectory => "Invalid config directory", + ConfigLoadError::InvalidParameter(_) => "Invalid parameter, use of reserved parameters in app-specific configs is not permitted", + ConfigLoadError::MissingName(_) => "The 'name' field is required in app-specific configurations, but it's missing", + ConfigLoadError::NameDuplicate(_) => "Found duplicate 'name' in some configurations, please use different names", + ConfigLoadError::UnableToCreateDefaultConfig => "Could not generate default config file", + } + } +} + + + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + use tempfile::{NamedTempFile, TempDir}; + + const TEST_WORKING_CONFIG_FILE : &str = include_str!("../res/test/working_config.yaml"); + const TEST_CONFIG_FILE_WITH_BAD_YAML : &str = include_str!("../res/test/config_with_bad_yaml.yaml"); + + // Test Configs + + fn create_tmp_file(string: &str) -> NamedTempFile { + let file = NamedTempFile::new().unwrap(); + file.as_file().write_all(string.as_bytes()); + file + } + + #[test] + fn test_config_file_not_found() { + let config = Configs::load_config(Path::new("invalid/path")); + assert_eq!(config.is_err(), true); + assert_eq!(config.unwrap_err(), ConfigLoadError::FileNotFound); + } + + #[test] + fn test_config_file_with_bad_yaml_syntax() { + let broken_config_file = create_tmp_file(TEST_CONFIG_FILE_WITH_BAD_YAML); + let config = Configs::load_config(broken_config_file.path()); + match config { + Ok(_) => {assert!(false)}, + Err(e) => { + match e { + ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, broken_config_file.path().to_owned()), + _ => assert!(false), + } + assert!(true); + }, + } + + } + + #[test] + fn test_validate_field_macro() { + let mut result = true; + + validate_field!(result, 3, 3); + assert_eq!(result, true); + + validate_field!(result, 10, 3); + assert_eq!(result, false); + + validate_field!(result, 3, 3); + assert_eq!(result, false); + } + + #[test] + fn test_specific_config_does_not_have_reserved_fields() { + let working_config_file = create_tmp_file(r###" + + backend: Clipboard + + "###); + let config = Configs::load_config(working_config_file.path()); + assert_eq!(config.unwrap().validate_specific_config(), true); + } + + #[test] + fn test_specific_config_has_reserved_fields_config_caching_interval() { + let working_config_file = create_tmp_file(r###" + + # This should not happen in an app-specific config + config_caching_interval: 100 + + "###); + let config = Configs::load_config(working_config_file.path()); + assert_eq!(config.unwrap().validate_specific_config(), false); + } + + #[test] + fn test_specific_config_has_reserved_fields_toggle_key() { + let working_config_file = create_tmp_file(r###" + + # This should not happen in an app-specific config + toggle_key: CTRL + + "###); + let config = Configs::load_config(working_config_file.path()); + assert_eq!(config.unwrap().validate_specific_config(), false); + } + + #[test] + fn test_specific_config_has_reserved_fields_toggle_interval() { + let working_config_file = create_tmp_file(r###" + + # This should not happen in an app-specific config + toggle_interval: 1000 + + "###); + let config = Configs::load_config(working_config_file.path()); + assert_eq!(config.unwrap().validate_specific_config(), false); + } + + #[test] + fn test_specific_config_has_reserved_fields_backspace_limit() { + let working_config_file = create_tmp_file(r###" + + # This should not happen in an app-specific config + backspace_limit: 10 + + "###); + let config = Configs::load_config(working_config_file.path()); + assert_eq!(config.unwrap().validate_specific_config(), false); + } + + #[test] + fn test_config_loaded_correctly() { + let working_config_file = create_tmp_file(TEST_WORKING_CONFIG_FILE); + let config = Configs::load_config(working_config_file.path()); + assert_eq!(config.is_ok(), true); + } + + // Test ConfigSet + + #[test] + fn test_config_set_default_content_should_work_correctly() { + let tmp_dir = TempDir::new().expect("unable to create temp directory"); + let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME); + fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT); + + let config_set = ConfigSet::load(tmp_dir.path()); + assert!(config_set.is_ok()); + } + + #[test] + fn test_config_set_load_fail_bad_directory() { + let config_set = ConfigSet::load(Path::new("invalid/path")); + assert_eq!(config_set.is_err(), true); + assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidConfigDirectory); + } + + #[test] + fn test_config_set_missing_default_file() { + let tmp_dir = TempDir::new().expect("unable to create temp directory"); + + let config_set = ConfigSet::load(tmp_dir.path()); + assert_eq!(config_set.is_err(), true); + assert_eq!(config_set.unwrap_err(), ConfigLoadError::FileNotFound); + } + + #[test] + fn test_config_set_invalid_yaml_syntax() { + let tmp_dir = TempDir::new().expect("unable to create temp directory"); + let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME); + let default_path_copy = default_path.clone(); + fs::write(default_path, TEST_CONFIG_FILE_WITH_BAD_YAML); + + let config_set = ConfigSet::load(tmp_dir.path()); + match config_set { + Ok(_) => {assert!(false)}, + Err(e) => { + match e { + ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, default_path_copy), + _ => assert!(false), + } + assert!(true); + }, + } + } + + #[test] + fn test_config_set_specific_file_with_reserved_fields() { + let tmp_dir = TempDir::new().expect("unable to create temp directory"); + let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME); + fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT); + + let specific_path = tmp_dir.path().join("specific.yaml"); + let specific_path_copy = specific_path.clone(); + fs::write(specific_path, r###" + config_caching_interval: 10000 + "###); + + let config_set = ConfigSet::load(tmp_dir.path()); + assert!(config_set.is_err()); + assert_eq!(config_set.unwrap_err(), ConfigLoadError::InvalidParameter(specific_path_copy)) + } + + #[test] + fn test_config_set_specific_file_missing_name() { + let tmp_dir = TempDir::new().expect("unable to create temp directory"); + let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME); + fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT); + + let specific_path = tmp_dir.path().join("specific.yaml"); + let specific_path_copy = specific_path.clone(); + fs::write(specific_path, r###" + backend: Clipboard + "###); + + let config_set = ConfigSet::load(tmp_dir.path()); + assert!(config_set.is_err()); + assert_eq!(config_set.unwrap_err(), ConfigLoadError::MissingName(specific_path_copy)) + } + + #[test] + fn test_config_set_specific_file_duplicate_name() { + let tmp_dir = TempDir::new().expect("unable to create temp directory"); + let default_path = tmp_dir.path().join(DEFAULT_CONFIG_FILE_NAME); + fs::write(default_path, DEFAULT_CONFIG_FILE_CONTENT); + + let specific_path = tmp_dir.path().join("specific.yaml"); + let specific_path_copy = specific_path.clone(); + fs::write(specific_path, r###" + name: specific1 + "###); + + let specific_path2 = tmp_dir.path().join("specific2.yaml"); + let specific_path_copy2 = specific_path2.clone(); + fs::write(specific_path2, r###" + name: specific1 + "###); + + let config_set = ConfigSet::load(tmp_dir.path()); + assert!(config_set.is_err()); + assert_eq!(config_set.unwrap_err(), ConfigLoadError::NameDuplicate(specific_path_copy2)) + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7d3f19..d1073d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,9 @@ use std::{thread, time}; use clap::{App, Arg}; use std::path::Path; use std::sync::mpsc::Receiver; -use log::{info, LevelFilter}; +use log::{info, error, LevelFilter}; use simplelog::{CombinedLogger, TermLogger, TerminalMode}; +use std::process::exit; mod ui; mod bridge; @@ -70,7 +71,10 @@ fn main() { info!("loading configuration from custom location: {}", path); ConfigSet::load(Path::new(path)) }, - }; + }.unwrap_or_else(|e| { + error!("{}", e); + exit(1); + }); if matches.is_present("dump") { println!("{:#?}", config_set); diff --git a/src/res/test/config_with_bad_yaml.yaml b/src/res/test/config_with_bad_yaml.yaml new file mode 100644 index 0000000..b5d6822 --- /dev/null +++ b/src/res/test/config_with_bad_yaml.yaml @@ -0,0 +1,12 @@ +backend: Clipboard + +definitely a bad yaml + +matches: + # Default + - trigger: ":espanso" + replace: "Hi there!" + + # Emojis + - trigger: ":lol" + replace: "😂" \ No newline at end of file diff --git a/src/res/test/working_config.yaml b/src/res/test/working_config.yaml new file mode 100644 index 0000000..bcf8bf6 --- /dev/null +++ b/src/res/test/working_config.yaml @@ -0,0 +1,10 @@ +backend: Clipboard + +matches: + # Default + - trigger: ":espanso" + replace: "Hi there!" + + # Emojis + - trigger: ":lol" + replace: "😂" \ No newline at end of file