1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//! # Quickstart
//!
//! Suppose you have the following file structure:
//!
//! ```text
//! ├── config
//! │   ├── .secrets.toml
//! │   └── settings.toml
//! └── your-executable
//! ```
//!
//! `settings.toml`:
//!
//! ```toml
//! [default]
//! pg.port = 5432
//! pg.host = 'localhost'
//!
//! [production]
//! pg.host = 'db-0'
//! ```
//!
//! `.secrets.toml`:
//!
//! ```toml
//! [default]
//! pg.password = 'a password'
//!
//! [production]
//! pg.password = 'a strong password'
//! ```
//!
//! Then, in your executable source (make sure to add `serde = { version = "1.0",
//! features = ["derive"] }` to your dependencies):
//!
//! ```no_run
//! use serde::Deserialize;
//! use hydroconf::Hydroconf;
//!
//! #[derive(Debug, Deserialize)]
//! struct Config {
//!     pg: PostgresConfig,
//! }
//!
//! #[derive(Debug, Deserialize)]
//! struct PostgresConfig {
//!     host: String,
//!     port: u16,
//!     password: String,
//! }
//!
//! fn main() {
//!     let conf: Config = match Hydroconf::default().hydrate() {
//!         Ok(c) => c,
//!         Err(e) => {
//!             println!("could not read configuration: {:#?}", e);
//!             std::process::exit(1);
//!         }
//!     };
//!
//!     println!("{:#?}", conf);
//! }
//! ```
//!
//! If you compile and execute the program (making sure the executable is in the
//! same directory where the `config` directory is), you will see the following:
//!
//! ```sh
//! $ ./your-executable
//! Config {
//!     pg: PostgresConfig {
//!         host: "localhost",
//!         port: 5432,
//!         password: "a password"
//!     }
//! }
//! ```
//!
//! Hydroconf will select the settings in the `[default]` table by default. If you
//! set `ENV_FOR_HYDRO` to `production`, Hydroconf will overwrite them with the
//! production ones:
//!
//! ```sh
//! $ ENV_FOR_HYDRO=production ./your-executable
//! Config {
//!     pg: PostgresConfig {
//!         host: "db-0",
//!         port: 5432,
//!         password: "a strong password"
//!     }
//! }
//! ```
//!
//! Settings can always be overridden with environment variables:
//!
//! ```bash
//! $ HYDRO_PG__PASSWORD="an even stronger password" ./your-executable
//! Config {
//!     pg: PostgresConfig {
//!         host: "localhost",
//!         port: 5432,
//!         password: "an even stronger password"
//!     }
//! }
//! ```
//! # Environment variables
//! There are two formats for the environment variables:
//!
//! 1. those that control how Hydroconf works have the form `*_FOR_HYDRO`;
//! 2. those that override values in your configuration have the form `HYDRO_*`.
//!
//! For example, to specify where Hydroconf should look for the configuration
//! files, you can set the variable `ROOT_PATH_FOR_HYDRO`. In that case, it's no
//! longer necessary to place the binary in the same directory as the
//! configuration. Hydroconf will search directly from the root path you specify.
//!
//! Here is a list of all the currently supported environment variables to
//! configure how Hydroconf works:
//!
//! * `ROOT_PATH_FOR_HYDRO`: specifies the location from which Hydroconf should
//!   start searching configuration files. By default, Hydroconf will start from
//!   the directory that contains your executable;
//! * `SETTINGS_FILE_FOR_HYDRO`: exact location of the main settings file;
//! * `SECRETS_FILE_FOR_HYDRO`: exact location of the file containing secrets;
//! * `ENV_FOR_HYDRO`: the environment to load after loading the `default` one
//!   (e.g. `development`, `testing`, `staging`, `production`, etc.). By default,
//!   Hydroconf will load the `development` environment, unless otherwise
//!   specified.
//! * `ENVVAR_PREFIX_FOR_HYDRO`: the prefix of the environement variables holding
//!   your configuration -- see group number 2. above. By default it's `HYDRO`
//!   (note that you don't have to include the `_` separator, as that is added
//!   automatically);
//! * `ENVVAR_NESTED_SEP_FOR_HYDRO`: the separator in the environment variables
//!   holding your configuration that signals a nesting point. By default it's `__`
//!   (double underscore), so if you set `HYDRO_REDIS__HOST=localhost`, Hydroconf
//!   will match it with the nested field `redis.host` in your configuration.
//!
//! # Hydroconf initialization
//! You can create a new Hydroconf struct in two ways.
//!
//! The first one is to use the `Hydroconf::default()` method, which will use the
//! default settings. The default constructor will attempt to load the settings
//! from the environment variables (those in the form `*_FOR_HYDRO`), and if it
//! doesn't find them it will use the default values. The alternative is to create
//! a `HydroSettings` struct manually and pass it to `Hydroconf`:
//!
//! ```rust
//! # use hydroconf::{Hydroconf, HydroSettings};
//!
//! let hydro_settings = HydroSettings::default()
//!     .set_envvar_prefix("MYAPP".into())
//!     .set_env("staging".into());
//! let hydro = Hydroconf::new(hydro_settings);
//! ```
//!
//! Note that `HydroSettings::default()` will still try to load the settings from
//! the environment before you overwrite them.
//!
//! # The hydration process
//! ## 1. Configuration loading
//! When you call `Hydroconf::hydrate()`, Hydroconf starts looking for your
//! configuration files and if it finds them, it loads them. The search starts from
//! `HydroSettings.root_path`; if the root path is not defined, Hydroconf will use
//! `std::env::current_exe()`. From this path, Hydroconf generates all the possible
//! candidates by walking up the directory tree, also searching in the `config`
//! subfolder at each level. For example, if the root path is
//! `/home/user/www/api-server/dist`, Hydroconf will try the following paths, in
//! this order:
//!
//! 1. `/home/user/www/api-server/dist/config`
//! 2. `/home/user/www/api-server/dist`
//! 3. `/home/user/www/api-server/config`
//! 4. `/home/user/www/api-server`
//! 5. `/home/user/www/config`
//! 6. `/home/user/www`
//! 7. `/home/user/config`
//! 8. `/home/user`
//! 9. `/home/config`
//! 10. `/home`
//! 11. `/config`
//! 12. `/`
//!
//! In each directory, Hydroconf will search for the files
//! `settings.{toml,json,yaml,ini,hjson}` and
//! `.secrets.{toml,json,yaml,ini,hjson}`. As soon as one of those (or both) are
//! found, the search stops and Hydroconf won't search the remaining upper levels.
//!
//! ## 2. Merging
//! In this step, Hydroconf merges the values from the different environments
//! from the configuration files discovered in the previous step. Hydroconf
//! first checks if an environment called `default` exists: in that case those
//! values are selected first. Then, it checks if the environment specified for
//! Hydroconf (`ENV_FOR_HYDRO`, or "development" if not specified) exists and in
//! that case it selects those values and merges them with the existing ones.
//!
//! ## 3. `.env` file overrides
//! In this step Hydroconf starts from the root path (the same one from step 1),
//! and walks the filesystem upward in search of an `.env` file. If it finds
//! one, it parses it and merges those values with the existing ones.
//!
//! ## 4. Environment variables overrides
//! In this step Hydroconf merges the values from all environment variables that
//! you defined with the Hydro prefix (`HYDRO_` by default, as explained in the
//! [previous section](#environment-variables)).
//!
//! ## 5. Deserialization
//! Finally, Hydroconf tries to deserialize the configuration into the return
//! type you specify, which should be your configuration struct.
//!
//! # Best practices
//! In order to keep your configuration simple, secure and effective, Hydroconf
//! makes it easy for you to follow these best practices:
//! 1. keep the non-secret values inside `config/settings.{toml,yaml,json,...}`
//!    separated by environment; in particular, define a "default" environment
//!    which contains the base settings, and specialize it in all the additional
//!    environments that you need (e.g. "development", "testing", "staging",
//!    "production", etc.);
//! 2. keep the secret values inside `config/.secrets.{toml,yaml,json,...}`
//!    separated by environment and exclude this file from version control;
//! 3. define the environment variable `ENV_FOR_DYNACONF` to specify which
//!    environment should be loaded (besides the "default" one, which is always
//!    loaded first);
//! 4. if you want to override some values, or specify some secret values which
//!    are not in the secret file, define the environment variables `HYDRO_*`
//!    (or use a custom prefix and define `ENVVAR_PREFIX_FOR_HYDRO`).

mod env;
mod hydro;
mod settings;
mod sources;
mod utils;

pub use hydro::{Config, ConfigError, Environment, File, Hydroconf};
pub use settings::HydroSettings;
pub use sources::FileSources;