aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 66fbdee2c263eba5d8a953a6d2b00fa885643d69 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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<PathBuf>,
}

impl Args {
    fn get_config_files(&self) -> Vec<PathBuf> {
        match self.config_file {
            Some(ref config_file) => vec![config_file.to_path_buf()],
            None => DefaultConfigFile::default().paths,
        }
    }
}

struct DefaultConfigFile {
    paths: Vec<PathBuf>,
}

impl DefaultConfigFile {
    fn user_home_config() -> AppResult<Option<PathBuf>> {
        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::<OsString>());

            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!");
    }
}