simpleconfig/
lib.rs

1use std::collections::HashMap;
2use urlencoding::decode;
3use core::str::FromStr;
4
5/// Config objects that supports passing by simple string
6/// 
7/// Configs can be read subsequently.
8/// 
9/// Example
10/// ```
11/// use crate::simpleconfig::Config;
12/// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
13/// 
14/// let my_config = Config::from("hello=world;bool_flag=true;float1=6.5;usize_key=53444;ip_address=127.0.0.1");
15/// assert_eq!("world", my_config.get_string("hello", ""));
16/// let bool_flag = my_config.get_bool("bool_flag", false);
17/// assert_eq!(true, bool_flag);
18/// 
19/// let float1:f64 = my_config.get_parse("float1", 0.4);
20/// assert_eq!(6.5, float1);
21/// 
22/// let usize_var = my_config.get_parse("usize_key", 13);
23/// assert_eq!(53444, usize_var);
24/// 
25/// let ip_addr = my_config.get_parse("ip_address", IpAddr::V4(Ipv4Addr::new(127, 2, 2, 1)));
26/// let intended = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
27/// 
28/// assert_eq!(intended, ip_addr);
29/// 
30/// let default_v = my_config.get_parse("not_defined", "default".to_string());
31/// assert_eq!("default", default_v);
32/// ```
33/// 
34/// 
35
36#[derive(Debug, Clone)]
37pub struct Config {
38    map: HashMap<String, String>
39}
40
41impl Config {
42    /// Create a config instance from AsConfig trait
43    pub fn from(what:impl AsConfig) -> Config {
44        return what.to_config();
45    }
46
47    /// Read parameter identified by `key` as `&str`. If not set, use the `default_value`.
48    pub fn get_string<'a, 'b>(&'a self, key:&'b str, default_value:&'b str) ->&'b str 
49        where 'a:'b
50    {
51        let result = self.map.get(key);
52        if result.is_none() {
53            return default_value;
54        }
55        return result.unwrap();
56    }
57
58    /// Read parameter identified by `key` as bool. If not set, use the `default_value`.
59    /// 
60    /// only `true` (case insensitive) is considered as true. Other values are `false`.
61    pub fn get_bool(&self, key:&str, default_value: bool) -> bool {
62        let str_value = self.get_string(key, "");
63        if str_value == "" {
64            return default_value;
65        }
66        return str_value.to_ascii_lowercase() == "true";
67    }
68
69    /// Read parameter identified by `key` as T (where T:FromStr). If not set, use `default_value`.
70    /// 
71    /// Typical `FromStr` includes all numbers (i32, usize, etc), or `IpAddress`, or any other supported type
72    /// that implements `FromStr` trait.
73    pub fn get_parse<T:FromStr>(&self, key:&str, default_value: T) -> T {
74        let str_value = self.get_string(key, "");
75        if str_value == "" {
76            return default_value;
77        }
78        let result = str_value.parse();
79        if result.is_err() {
80            return default_value;
81        }
82        let result =  result.ok().unwrap();
83        return result;
84    }
85
86    fn url_decode(input:&str) -> String {
87        let decoded = decode(input).expect("UTF-8");
88        return decoded.to_string();
89    }
90}
91
92
93/// If your type can be converted to Config, you should implement the AsConfig trait
94pub trait AsConfig {
95    fn to_config(self) -> Config;
96}
97
98/// AsConfig is automatically implemented for all Into<Config> trait objects
99impl<T> AsConfig for T where 
100T:Into<Config> {
101    fn to_config(self) -> Config {
102        return self.into();
103    }
104}
105
106/// Config can be derived from &str in "key=value;key2=value2;" format.
107/// If value contains special charactor you can encode it with "%%:" + URLEncode(value)
108impl From<&str> for Config {
109    fn from(what:&str) -> Self {
110        return what.to_string().into();
111    }
112}
113
114/// Config can be derived from &str in "key=value;key2=value2;" format.
115/// If value contains special charactor you can encode it with "%%:" + URLEncode(value)
116impl From<String> for Config {
117    /// `what` must be "key=value;key1=value1" format. Empty tokens (e.g. "key=value;;;") are skipped.
118    /// If you need to specify values that may contain special characters (e.g. include`;` or `=`), you can use
119    /// '%%:' to prefix your value, and the value should be url encoded. 
120    /// For example format of `key=%%:%3B%3B%3B;key2=%%:%3B%3B%3B" => key = ";;;", key2 = ";;;"
121    /// 
122    /// What if your key should be "%%:123"? 
123    /// 
124    /// No worries, "%%:123" => "%%:%25%25%3A123"
125    /// 
126    /// Keys and values are trimed automatically. If you need to start/end with space, you should encode as %20.
127    fn from(what: String) -> Self {
128        let tokens = what.split(";").filter(|x| x.trim().len() > 0);
129        let mut map = HashMap::<String, String>::new();
130        for next in tokens {
131            let equal_pos = next.find("=");
132            if equal_pos.is_none() {
133                continue;
134            }
135            let equal_pos = equal_pos.unwrap();
136
137            let first = next[0..equal_pos].trim();
138            let mut second = next[equal_pos + 1..].trim();
139            let actual_value:String;
140            if second.starts_with("%%:") {
141                second = &second[3..];
142                actual_value = Config::url_decode(second);
143            } else {
144                actual_value = second.into();
145            }
146
147            map.insert(first.into(), actual_value);
148        }
149
150        return Config{map};
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn it_works() {
160        let config = Config::from("hello=world;a=aa;c=%%:%20hello%20;key=true");
161        println!("{config:?}");
162        let a:bool = config.get_parse("key", false);
163        println!("{a}");
164    }
165}