diff --git a/Cargo.lock b/Cargo.lock index a54f055..ed86b11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3873,6 +3873,7 @@ dependencies = [ "serde", "serde_default", "serde_json", + "tempfile", "tokio", "toml 0.7.5", "wayland-backend", diff --git a/src/config.rs b/src/config.rs index 829f6e8..80cdb14 100644 --- a/src/config.rs +++ b/src/config.rs @@ -111,13 +111,9 @@ pub fn from_cli() -> anyhow::Result { pub fn new_with_cli(matches: &ArgMatches) -> anyhow::Result { let mut config_path = None; if matches.contains_id("config") { - let config_file = matches.get_one::("config"); + let config_file = matches.get_one::("config"); if let Some(path) = config_file { - if let Err(e) = std::fs::metadata(path) { - warn!("Invalid config filename, using the default config: {e}"); - } else { - config_path = Some(Path::new(path).to_path_buf()); - } + config_path = Some(Path::new(path).to_path_buf()); } } let mut config = FileConfig::new(config_path)?; diff --git a/watchers/Cargo.toml b/watchers/Cargo.toml index de8c254..9714bef 100644 --- a/watchers/Cargo.toml +++ b/watchers/Cargo.toml @@ -11,6 +11,7 @@ path = "src/lib.rs" [dev-dependencies] rstest = "0.17.0" +tempfile = "3.5.0" [dependencies] aw-client-rust = { git = "https://github.com/2e3s/aw-server-rust", rev = "81db4c8" } diff --git a/watchers/src/config/file_config.rs b/watchers/src/config/file_config.rs index dc4dea3..4b4fa79 100644 --- a/watchers/src/config/file_config.rs +++ b/watchers/src/config/file_config.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Context}; use serde::Deserialize; use serde_default::DefaultFromSerde; -use std::{io::ErrorKind, path::PathBuf, time::Duration}; +use std::{fs, io::ErrorKind, path::PathBuf, time::Duration}; use crate::config::defaults; @@ -97,8 +97,15 @@ pub struct FileConfig { impl FileConfig { pub fn new(config_override: Option) -> anyhow::Result { + let is_config_overridden = config_override.is_some(); let config_path = if let Some(config_override) = config_override { - config_override + if config_override.starts_with("~/") { + dirs::home_dir() + .ok_or(anyhow!("Home directory is not found"))? + .join(config_override.strip_prefix("~").unwrap()) + } else { + config_override + } } else { let mut system_config_path: PathBuf = dirs::config_dir().ok_or(anyhow!("Config directory is unknown"))?; @@ -108,7 +115,7 @@ impl FileConfig { system_config_path }; - let mut config = if config_path.exists() { + let mut config = if fs::metadata(&config_path).is_ok() { debug!("Reading config at {}", config_path.display()); let config_content = std::fs::read_to_string(&config_path).with_context(|| { format!("Impossible to read config file {}", config_path.display()) @@ -116,6 +123,9 @@ impl FileConfig { toml::from_str(&config_content)? } else { + if is_config_overridden { + anyhow::bail!("Config file is not accessible at {}", config_path.display()); + } let config = default_config(); let error = std::fs::create_dir(config_path.parent().unwrap()); if let Err(e) = error { @@ -133,3 +143,102 @@ impl FileConfig { Ok(config) } } + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + use std::io::Write; + use tempfile::NamedTempFile; + + #[rstest] + fn all() { + let mut file = NamedTempFile::new().unwrap(); + write!( + file, + r#" +[server] +port = 1234 +host = "http://address.com" + +[awatcher] +idle-timeout-seconds=14 +poll-time-idle-seconds=13 +poll-time-window-seconds=12 + +# Add as many filters as needed. +# There should be at least 1 match field, and at least 1 replace field. +[[awatcher.filters]] +match-app-id = "firefox" +replace-title = "Unknown" + +[[awatcher.filters]] +match-app-id = "code" +match-title = "title" +replace-app-id = "VSCode" +replace-title = "Title" + "# + ) + .unwrap(); + + let config = FileConfig::new(Some(file.path().to_path_buf())).unwrap(); + + assert_eq!(1234, config.server.port); + assert_eq!("http://address.com", config.server.host); + + assert_eq!(14, config.client.idle_timeout_seconds); + assert_eq!(13, config.client.poll_time_idle_seconds); + assert_eq!(12, config.client.poll_time_window_seconds); + + assert_eq!(2, config.client.filters.len()); + let replacement1 = config.client.filters[0] + .replacement("firefox", "any") + .unwrap(); + assert_eq!(None, replacement1.replace_app_id); + assert_eq!(Some("Unknown".to_string()), replacement1.replace_title); + let replacement2 = config.client.filters[1] + .replacement("code", "title") + .unwrap(); + assert_eq!(Some("VSCode".to_string()), replacement2.replace_app_id); + assert_eq!(Some("Title".to_string()), replacement2.replace_title); + } + + #[rstest] + fn empty() { + let mut file = NamedTempFile::new().unwrap(); + write!(file, "[awatcher]").unwrap(); + + let config = FileConfig::new(Some(file.path().to_path_buf())).unwrap(); + + assert_eq!(defaults::port(), config.server.port); + assert_eq!(defaults::host(), config.server.host); + + assert_eq!( + defaults::idle_timeout_seconds(), + config.client.idle_timeout_seconds + ); + assert_eq!( + defaults::poll_time_idle_seconds(), + config.client.poll_time_idle_seconds + ); + assert_eq!( + defaults::poll_time_window_seconds(), + config.client.poll_time_window_seconds + ); + + assert_eq!(0, config.client.filters.len()); + } + + #[rstest] + fn wrong_file() { + let file = PathBuf::new(); + + let config = FileConfig::new(Some(file)); + + assert!(config.is_err()); + assert_eq!( + "Config file is not accessible at ", + config.err().unwrap().to_string() + ); + } +}