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}