use crate::config::Config; use crate::error::{AppError, AppResult}; use crate::ip_service::IpService; use clap::Parser; use homedir::my_home; use log::info; use reqwest::ClientBuilder; use std::path::PathBuf; use std::process::exit; mod config; mod dyndns_service; mod error; mod ip_service; #[cfg(test)] mod test_macros; #[derive(Parser, Debug)] #[command(ignore_errors(true), version, about, long_about = None)] struct Args { #[arg(short, long)] config_file: Option, } impl Args { fn get_config_files(&self) -> Vec { match self.config_file { Some(ref config_file) => vec![config_file.to_path_buf()], None => DefaultConfigFile::default().paths, } } } struct DefaultConfigFile { paths: Vec, } impl DefaultConfigFile { fn user_home_config() -> AppResult> { let home = my_home().map_err(AppError::UnableToGetHomeDirectory)?; match home { None => Ok(None), Some(mut path) => { path.push(".config/multiwan-dyndns/config.json"); Ok(Some(path)) } } } } impl Default for DefaultConfigFile { fn default() -> Self { let mut paths = Vec::new(); paths.push(PathBuf::from("/etc/multiwan-dyndns.json")); if let Some(user_home) = Self::user_home_config().unwrap() { paths.push(user_home); } Self { paths } } } #[tokio::main] async fn main() { env_logger::init(); let args = Args::parse(); match run(args).await { Ok(_) => exit(0), Err(e) => { log::error!("{}", e); exit(1); } } } async fn run(args: Args) -> AppResult<()> { let config = Config::load(args.get_config_files())?; let client = ClientBuilder::new().build()?; for network in config.networks { let ip = network.ip_service.resolve(&client).await?; info!("{:?}: {}", network.interface, ip); // for dyndns in network.providers { // DynDnsService::register(&dyndns, &ip)?; // } } Ok(()) } #[cfg(test)] mod tests { use super::*; use homedir::my_home; use std::ffi::OsString; use std::iter; use std::path::Path; macro_rules! assert_contains_config { ($config:expr, $expected:expr) => { assert!( $config.contains(&$expected.to_path_buf()), "Expected {} path {:?}\nActual paths: {:?}", stringify!($expected), $expected, $config ); }; } test! { fn verify_default_config_path_is_in_home_directory() { let args = Args::parse_from(iter::empty::()); let global_config = Path::new("/etc/multiwan-dyndns.json"); let user_config_path = format!("{}/.config/multiwan-dyndns/config.json", my_home()?.unwrap().to_string_lossy()); let user_config = Path::new(&user_config_path); let actual = args.get_config_files(); assert_contains_config!(actual, user_config); assert_contains_config!(actual, global_config); } } #[tokio::test] async fn verify_it_actually_runs() { let args = vec!["multiwan-dyndns", "--config-file", "test/noop.json"]; run(Args::parse_from(args)).await.expect("app didn't run!"); } }