httpcli/
config.rs

1use std::{process, str::FromStr};
2
3use http::{HttpMethod, Result};
4
5#[derive(Debug, Clone)]
6pub enum OutFile {
7    Stdout,
8    Filename(String),
9    GetFromUrl,
10}
11
12#[derive(Clone, Debug)]
13pub struct ClientConfig {
14    pub url: String,
15    pub method: HttpMethod,
16    pub host: String,
17    pub port: u16,
18    pub user_agent: String,
19    pub out_file: OutFile,
20}
21
22impl ClientConfig {
23    /// Parse the configuration from the command line args
24    pub fn parse<I>(args: I) -> Result<Self>
25    where
26        I: Iterator<Item = String>,
27    {
28        let mut conf = Self::default();
29        let mut args = args.into_iter();
30
31        while let Some(arg) = args.next() {
32            macro_rules! parse_next {
33                () => {
34                    args.next_parse().ok_or_else(|| {
35                        format!("Missing or incorrect argument for \"{}\"", arg.as_str())
36                    })?
37                };
38                (as $t:ty) => {{
39                    let _next: $t = parse_next!();
40                    _next
41                }};
42            }
43
44            match arg.as_str() {
45                "-m" | "--method" => conf.method = parse_next!(),
46                "--host" => conf.host = parse_next!(),
47                "-a" | "--user-agent" => conf.user_agent = parse_next!(),
48                "-h" | "--help" => help(),
49                "-O" => conf.out_file = OutFile::GetFromUrl,
50                "-o" => conf.out_file = OutFile::Filename(parse_next!()),
51                _ => conf.url = arg,
52            }
53        }
54
55        let mut host = conf.url.as_str();
56        host = conf.url.strip_prefix("http://").unwrap_or(host);
57
58        let mut url = "/";
59
60        if let Some(i) = host.find('/') {
61            url = &host[i..];
62            host = &host[..i];
63        }
64
65        if let Some(i) = host.find(':') {
66            conf.port = host[i + 1..].parse()?;
67            host = &host[..i];
68        }
69
70        conf.host = host.to_string();
71        conf.url = url.to_string();
72
73        if conf.host.is_empty() {
74            return Err("Missing host".into());
75        }
76
77        Ok(conf)
78    }
79}
80
81fn help() -> ! {
82    println!("TODO");
83    process::exit(0);
84}
85
86trait ParseIterator {
87    fn next_parse<T: FromStr>(&mut self) -> Option<T>;
88}
89
90impl<I> ParseIterator for I
91where
92    I: Iterator<Item = String>,
93{
94    fn next_parse<T: FromStr>(&mut self) -> Option<T> {
95        self.next()?.parse().ok()
96    }
97}
98
99impl Default for ClientConfig {
100    /// Default configuration
101    ///
102    /// - Port: 80
103    /// - NÂș Workers: 1024
104    /// - Keep Alive Timeout: 0s (Disabled)
105    /// - Keep Alove Requests: 10000
106    #[inline]
107    fn default() -> Self {
108        Self {
109            method: HttpMethod::GET,
110            url: String::new(),
111            port: 80,
112            host: String::new(),
113            user_agent: "http-client".to_string(),
114            out_file: OutFile::Stdout,
115        }
116    }
117}
118
119#[cfg(test)]
120mod test {
121    #![allow(clippy::unwrap_used)]
122
123    use super::ClientConfig;
124    use http::Result;
125
126    fn parse_from_vec(v: &[&str]) -> Result<ClientConfig> {
127        let conf = v.iter().map(|s| (*s).to_string());
128        ClientConfig::parse(conf)
129    }
130
131    #[test]
132    fn unknown() {
133        let conf = vec!["?unknown"];
134        let conf = parse_from_vec(&conf).unwrap();
135        assert_eq!(conf.host, "?unknown");
136    }
137}