diff --git a/examples/todo/Rocket.toml b/examples/todo/Rocket.toml index f1b2c1c1..f14a995d 100644 --- a/examples/todo/Rocket.toml +++ b/examples/todo/Rocket.toml @@ -1,8 +1,2 @@ -[dev] -template_dir = "static" - -[stage] -template_dir = "static" - -[prod] +[global] template_dir = "static" diff --git a/lib/src/config/config.rs b/lib/src/config/config.rs index ea1ff46c..2e89c14d 100644 --- a/lib/src/config/config.rs +++ b/lib/src/config/config.rs @@ -21,7 +21,7 @@ pub struct Config { /// The environment that this configuration corresponds to. pub env: Environment, session_key: RwLock>, - extra: HashMap, + extras: HashMap, filename: String, } @@ -48,7 +48,7 @@ impl Config { port: 8000, log_level: LoggingLevel::Normal, session_key: RwLock::new(None), - extra: HashMap::new(), + extras: HashMap::new(), env: env, filename: filename.to_string(), } @@ -59,7 +59,7 @@ impl Config { port: 80, log_level: LoggingLevel::Normal, session_key: RwLock::new(None), - extra: HashMap::new(), + extras: HashMap::new(), env: env, filename: filename.to_string(), } @@ -70,7 +70,7 @@ impl Config { port: 80, log_level: LoggingLevel::Critical, session_key: RwLock::new(None), - extra: HashMap::new(), + extras: HashMap::new(), env: env, filename: filename.to_string(), } @@ -116,7 +116,7 @@ impl Config { "log level ('normal', 'critical', 'debug')")) }; } else { - self.extra.insert(name.into(), val.clone()); + self.extras.insert(name.into(), val.clone()); } Ok(()) @@ -130,26 +130,26 @@ impl Config { #[inline(always)] pub fn extras<'a>(&'a self) -> impl Iterator { - self.extra.iter() + self.extras.iter() } pub fn get_str<'a>(&'a self, name: &str) -> config::Result<&'a str> { - let value = self.extra.get(name).ok_or_else(|| ConfigError::NotFound)?; + let value = self.extras.get(name).ok_or_else(|| ConfigError::NotFound)?; parse!(self, name, value, as_str, "a string") } pub fn get_int<'a>(&'a self, name: &str) -> config::Result { - let value = self.extra.get(name).ok_or_else(|| ConfigError::NotFound)?; + let value = self.extras.get(name).ok_or_else(|| ConfigError::NotFound)?; parse!(self, name, value, as_integer, "an integer") } pub fn get_bool<'a>(&'a self, name: &str) -> config::Result { - let value = self.extra.get(name).ok_or_else(|| ConfigError::NotFound)?; + let value = self.extras.get(name).ok_or_else(|| ConfigError::NotFound)?; parse!(self, name, value, as_bool, "a boolean") } pub fn get_float<'a>(&'a self, name: &str) -> config::Result { - let value = self.extra.get(name).ok_or_else(|| ConfigError::NotFound)?; + let value = self.extras.get(name).ok_or_else(|| ConfigError::NotFound)?; parse!(self, name, value, as_float, "a float") } @@ -191,6 +191,12 @@ impl Config { self.env = var; self } + + #[inline(always)] + pub fn extra(mut self, name: &str, value: &Value) -> Self { + self.extras.insert(name.into(), value.clone()); + self + } } impl fmt::Debug for Config { @@ -207,7 +213,7 @@ impl PartialEq for Config { && self.port == other.port && self.log_level == other.log_level && self.env == other.env - && self.extra == other.extra + && self.extras == other.extras && self.filename == other.filename } } diff --git a/lib/src/config/environment.rs b/lib/src/config/environment.rs index 01bc9b31..a24b8ead 100644 --- a/lib/src/config/environment.rs +++ b/lib/src/config/environment.rs @@ -36,6 +36,12 @@ impl Environment { pub fn valid() -> &'static str { "development, staging, production" } + + /// Returns a list of all of the possible environments. + #[inline(always)] + pub fn all() -> [Environment; 3] { + [Development, Staging, Production] + } } impl FromStr for Environment { diff --git a/lib/src/config/error.rs b/lib/src/config/error.rs index 7c43085f..ee6993a2 100644 --- a/lib/src/config/error.rs +++ b/lib/src/config/error.rs @@ -58,9 +58,10 @@ impl ConfigError { info_!("{}", reason); } BadEntry(ref name, ref filename) => { + let valid_entries = format!("{}, and global", valid_envs); error!("[{}] is not a known configuration environment", name); info_!("in {}", White.paint(filename)); - info_!("valid environments are: {}", White.paint(valid_envs)); + info_!("valid environments are: {}", White.paint(valid_entries)); } BadEnv(ref name) => { error!("'{}' is not a valid ROCKET_ENV value", name); diff --git a/lib/src/config/mod.rs b/lib/src/config/mod.rs index c91467c9..d3f36d37 100644 --- a/lib/src/config/mod.rs +++ b/lib/src/config/mod.rs @@ -47,11 +47,12 @@ //! each environment. The file is optional. If it is not present, the default //! configuration parameters are used. //! -//! The file must be a series of tables, one for each environment, where each -//! table contains key-value pairs corresponding to configuration parameters for -//! that environment. If a configuration parameter is missing, the default value -//! is used. The following is a complete `Rocket.toml` file, where every -//! standard configuration parameter is specified with the default value: +//! The file must be a series of tables, at most one for each environment and a +//! "global" table, where each table contains key-value pairs corresponding to +//! configuration parameters for that environment. If a configuration parameter +//! is missing, the default value is used. The following is a complete +//! `Rocket.toml` file, where every standard configuration parameter is +//! specified with the default value: //! //! ```toml //! [development] @@ -74,6 +75,23 @@ //! session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz" //! ``` //! +//! The "global" pseudo-environment can be used to set and/or override +//! configuration parameters globally. A parameter defined in a `[global]` table +//! sets, or overrides if already present, that parameter in every environment. +//! For example, given the following `Rocket.toml` file, the value of `address` +//! will be `"1.2.3.4"` in every environment: +//! +//! ```toml +//! [global] +//! address = "1.2.3.4" +//! +//! [devolopment] +//! address = "localhost" +//! +//! [production] +//! address = "0.0.0.0" +//! ``` +//! //! ## Retrieving Configuration Parameters //! //! Configuration parameters for the currently active configuration environment @@ -127,6 +145,8 @@ static mut CONFIG: Option = None; const CONFIG_FILENAME: &'static str = "Rocket.toml"; +const GLOBAL_ENV_NAME: &'static str = "global"; + /// Wraps `std::result` with the error type of /// [ConfigError](enum.ConfigError.html). pub type Result = ::std::result::Result; @@ -198,16 +218,14 @@ impl RocketConfig { }).collect() ))?; - // Create a config with the defaults, but the set the env to the active + // Create a config with the defaults; set the env to the active one. let mut config = RocketConfig::active_default(filename)?; + // Store all of the global overrides, if any, for later use. + let mut global = None; + // Parse the values from the TOML file. for (entry, value) in toml { - // Parse the environment from the table entry name. - let env = entry.as_str().parse().map_err(|_| { - ConfigError::BadEntry(entry.clone(), filename.into()) - })?; - // Each environment must be a table. let kv_pairs = match value.as_table() { Some(table) => table, @@ -216,8 +234,24 @@ impl RocketConfig { )) }; - // Set the environment configuration from the kv pairs. - config.set(env, &kv_pairs)?; + if entry.as_str() == GLOBAL_ENV_NAME { + global = Some(kv_pairs.clone()); + } else { + // Parse the environment from the table entry name. + let env = entry.as_str().parse().map_err(|_| { + ConfigError::BadEntry(entry.clone(), filename.into()) + })?; + + // Set the environment configuration from the kv pairs. + config.set(env, &kv_pairs)?; + } + } + + // Override all of the environments with the global values. + if let Some(ref global_kv_pairs) = global { + for env in &Environment::all() { + config.set(*env, global_kv_pairs)?; + } } Ok(config) @@ -320,7 +354,7 @@ mod test { use std::env; use std::sync::Mutex; - use super::{RocketConfig, ConfigError}; + use super::{RocketConfig, ConfigError, GLOBAL_ENV_NAME}; use super::environment::{Environment, CONFIG_ENV}; use super::Environment::*; use super::config::Config; @@ -678,4 +712,42 @@ mod test { session_key = "abcv" = other "#.to_string(), TEST_CONFIG_FILENAME).is_err()); } + + #[test] + fn test_global_overrides() { + // Take the lock so changing the environment doesn't cause races. + let _env_lock = ENV_LOCK.lock().unwrap(); + + // Test first that we can override each environment. + for env in &Environment::all() { + env::set_var(CONFIG_ENV, env.to_string()); + + check_config!(RocketConfig::parse(format!(r#" + [{}] + address = "7.6.5.4" + "#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), { + default_config(*env).address( + "7.6.5.4".into() + ) + }); + + check_config!(RocketConfig::parse(format!(r#" + [{}] + database = "mysql" + "#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), { + default_config(*env).extra("database", + &Value::String("mysql".into()) + ) + }); + + check_config!(RocketConfig::parse(format!(r#" + [{}] + port = 3980 + "#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), { + default_config(*env).port( + 3980 + ) + }); + } + } }