icee_config_rs/lib.rs
1use std::env::var;
2use std::fs::read_dir;
3
4use config::{Config, Environment, File};
5use log::{debug, info};
6use serde::Deserialize;
7
8/// Configuration loader options
9pub struct Options {
10 /// Root config folder (defaults to 'config')
11 pub root_path: String,
12 /// If not set, use APP_ENV environment value or defaults to 'dev'
13 pub env: Option<String>,
14 /// Loads env variables (defaults to true)
15 pub read_envs: bool,
16 /// Environment var separator
17 pub env_separator: Option<String>,
18}
19
20/// Loads a configuration
21///
22/// Requires predefined struct with all config options
23///
24/// ```
25/// use serde::Deserialize;
26/// use icee_config_rs::{load, Options};
27///
28/// #[derive(Deserialize)]
29/// struct C {
30/// pub value: Option<String>,
31/// }
32///
33/// let parsed = load::<C>(Options::new());
34/// # assert_eq!(parsed.value, None);
35/// ```
36pub fn load<'a, T: Deserialize<'a>>(options: Options) -> T {
37 info!("Parsing configuration...");
38 let mut config = Config::new();
39
40 load_dir(&mut config, &options.root_path);
41
42 let env: String = match options.env {
43 Some(v) => v,
44 None => var("APP_ENV").unwrap_or_else(|_| "dev".into())
45 };
46
47 load_dir(&mut config, &format!("{}/{}", &options.root_path, env));
48
49 let mut env_opt = Environment::new();
50 if let Some(separator) = options.env_separator {
51 env_opt = env_opt.separator(&separator);
52 }
53
54 config.merge(env_opt).unwrap();
55
56 config.try_into().expect("loading configuration")
57}
58
59/// Options for loading configuration files
60impl Options {
61 /// Prepare default options
62 ///
63 /// ```
64 /// use icee_config_rs::Options;
65 ///
66 /// let opt = Options::new();
67 /// # assert_eq!(opt.env, None);
68 /// # assert_eq!(opt.root_path, "config");
69 /// # assert_eq!(opt.read_envs, true);
70 /// ```
71 pub fn new() -> Self {
72 Options {
73 root_path: "config".into(),
74 env: None,
75 read_envs: true,
76 env_separator: Some("__".into()),
77 }
78 }
79
80 /// Default options with different root path
81 ///
82 /// ```
83 /// use icee_config_rs::Options;
84 ///
85 /// let opt = Options::with_root_path("conf");
86 /// # assert_eq!(opt.env, None);
87 /// # assert_eq!(opt.root_path, "conf");
88 /// # assert_eq!(opt.read_envs, true);
89 /// ```
90 pub fn with_root_path(root_path: &str) -> Self {
91 Options {
92 root_path: root_path.into(),
93 env: None,
94 read_envs: true,
95 env_separator: Some("__".into()),
96 }
97 }
98
99 /// Default options with different env settings
100 ///
101 /// ```
102 /// use icee_config_rs::Options;
103 ///
104 /// let opt = Options::with_app_env("test");
105 /// # assert_eq!(opt.env, Some("test".into()));
106 /// # assert_eq!(opt.root_path, "config");
107 /// # assert_eq!(opt.read_envs, true);
108 /// ```
109 pub fn with_app_env(env: &str) -> Self {
110 Options {
111 root_path: "config".into(),
112 env: Some(env.into()),
113 read_envs: true,
114 env_separator: Some("__".into()),
115 }
116 }
117}
118
119impl Default for Options {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125fn load_dir(config: &mut Config, dirname: &str) {
126 let supported = ["toml", "ini", "json", "hjson", "yaml", "yml"];
127 debug!("Walking {} config directory...", dirname);
128
129 if let Ok(dir) = read_dir(dirname) {
130 for path in dir {
131 let path = path.unwrap().path();
132 let filename = path.to_str().unwrap();
133
134 if let Some(ext) = filename.split('.').last() {
135 if supported.contains(&ext) {
136 config
137 .merge(File::with_name(filename))
138 .unwrap_or_else(|_| panic!("invalid config file {}", filename));
139 }
140 }
141 }
142 }
143}