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}