Add the [global] psuedo-environment for global configuration.

This commit is contained in:
Sergio Benitez 2016-10-31 17:00:32 +01:00
parent da7cb44671
commit d91e3e0454
5 changed files with 112 additions and 33 deletions

View File

@ -1,8 +1,2 @@
[dev] [global]
template_dir = "static"
[stage]
template_dir = "static"
[prod]
template_dir = "static" template_dir = "static"

View File

@ -21,7 +21,7 @@ pub struct Config {
/// The environment that this configuration corresponds to. /// The environment that this configuration corresponds to.
pub env: Environment, pub env: Environment,
session_key: RwLock<Option<String>>, session_key: RwLock<Option<String>>,
extra: HashMap<String, Value>, extras: HashMap<String, Value>,
filename: String, filename: String,
} }
@ -48,7 +48,7 @@ impl Config {
port: 8000, port: 8000,
log_level: LoggingLevel::Normal, log_level: LoggingLevel::Normal,
session_key: RwLock::new(None), session_key: RwLock::new(None),
extra: HashMap::new(), extras: HashMap::new(),
env: env, env: env,
filename: filename.to_string(), filename: filename.to_string(),
} }
@ -59,7 +59,7 @@ impl Config {
port: 80, port: 80,
log_level: LoggingLevel::Normal, log_level: LoggingLevel::Normal,
session_key: RwLock::new(None), session_key: RwLock::new(None),
extra: HashMap::new(), extras: HashMap::new(),
env: env, env: env,
filename: filename.to_string(), filename: filename.to_string(),
} }
@ -70,7 +70,7 @@ impl Config {
port: 80, port: 80,
log_level: LoggingLevel::Critical, log_level: LoggingLevel::Critical,
session_key: RwLock::new(None), session_key: RwLock::new(None),
extra: HashMap::new(), extras: HashMap::new(),
env: env, env: env,
filename: filename.to_string(), filename: filename.to_string(),
} }
@ -116,7 +116,7 @@ impl Config {
"log level ('normal', 'critical', 'debug')")) "log level ('normal', 'critical', 'debug')"))
}; };
} else { } else {
self.extra.insert(name.into(), val.clone()); self.extras.insert(name.into(), val.clone());
} }
Ok(()) Ok(())
@ -130,26 +130,26 @@ impl Config {
#[inline(always)] #[inline(always)]
pub fn extras<'a>(&'a self) -> impl Iterator<Item=(&'a String, &'a Value)> { pub fn extras<'a>(&'a self) -> impl Iterator<Item=(&'a String, &'a Value)> {
self.extra.iter() self.extras.iter()
} }
pub fn get_str<'a>(&'a self, name: &str) -> config::Result<&'a str> { 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") parse!(self, name, value, as_str, "a string")
} }
pub fn get_int<'a>(&'a self, name: &str) -> config::Result<i64> { pub fn get_int<'a>(&'a self, name: &str) -> config::Result<i64> {
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") parse!(self, name, value, as_integer, "an integer")
} }
pub fn get_bool<'a>(&'a self, name: &str) -> config::Result<bool> { pub fn get_bool<'a>(&'a self, name: &str) -> config::Result<bool> {
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") parse!(self, name, value, as_bool, "a boolean")
} }
pub fn get_float<'a>(&'a self, name: &str) -> config::Result<f64> { pub fn get_float<'a>(&'a self, name: &str) -> config::Result<f64> {
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") parse!(self, name, value, as_float, "a float")
} }
@ -191,6 +191,12 @@ impl Config {
self.env = var; self.env = var;
self 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 { impl fmt::Debug for Config {
@ -207,7 +213,7 @@ impl PartialEq for Config {
&& self.port == other.port && self.port == other.port
&& self.log_level == other.log_level && self.log_level == other.log_level
&& self.env == other.env && self.env == other.env
&& self.extra == other.extra && self.extras == other.extras
&& self.filename == other.filename && self.filename == other.filename
} }
} }

View File

@ -36,6 +36,12 @@ impl Environment {
pub fn valid() -> &'static str { pub fn valid() -> &'static str {
"development, staging, production" "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 { impl FromStr for Environment {

View File

@ -58,9 +58,10 @@ impl ConfigError {
info_!("{}", reason); info_!("{}", reason);
} }
BadEntry(ref name, ref filename) => { BadEntry(ref name, ref filename) => {
let valid_entries = format!("{}, and global", valid_envs);
error!("[{}] is not a known configuration environment", name); error!("[{}] is not a known configuration environment", name);
info_!("in {}", White.paint(filename)); info_!("in {}", White.paint(filename));
info_!("valid environments are: {}", White.paint(valid_envs)); info_!("valid environments are: {}", White.paint(valid_entries));
} }
BadEnv(ref name) => { BadEnv(ref name) => {
error!("'{}' is not a valid ROCKET_ENV value", name); error!("'{}' is not a valid ROCKET_ENV value", name);

View File

@ -47,11 +47,12 @@
//! each environment. The file is optional. If it is not present, the default //! each environment. The file is optional. If it is not present, the default
//! configuration parameters are used. //! configuration parameters are used.
//! //!
//! The file must be a series of tables, one for each environment, where each //! The file must be a series of tables, at most one for each environment and a
//! table contains key-value pairs corresponding to configuration parameters for //! "global" table, where each table contains key-value pairs corresponding to
//! that environment. If a configuration parameter is missing, the default value //! configuration parameters for that environment. If a configuration parameter
//! is used. The following is a complete `Rocket.toml` file, where every //! is missing, the default value is used. The following is a complete
//! standard configuration parameter is specified with the default value: //! `Rocket.toml` file, where every standard configuration parameter is
//! specified with the default value:
//! //!
//! ```toml //! ```toml
//! [development] //! [development]
@ -74,6 +75,23 @@
//! session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz" //! 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 //! ## Retrieving Configuration Parameters
//! //!
//! Configuration parameters for the currently active configuration environment //! Configuration parameters for the currently active configuration environment
@ -127,6 +145,8 @@ static mut CONFIG: Option<RocketConfig> = None;
const CONFIG_FILENAME: &'static str = "Rocket.toml"; const CONFIG_FILENAME: &'static str = "Rocket.toml";
const GLOBAL_ENV_NAME: &'static str = "global";
/// Wraps `std::result` with the error type of /// Wraps `std::result` with the error type of
/// [ConfigError](enum.ConfigError.html). /// [ConfigError](enum.ConfigError.html).
pub type Result<T> = ::std::result::Result<T, ConfigError>; pub type Result<T> = ::std::result::Result<T, ConfigError>;
@ -198,16 +218,14 @@ impl RocketConfig {
}).collect() }).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)?; 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. // Parse the values from the TOML file.
for (entry, value) in toml { 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. // Each environment must be a table.
let kv_pairs = match value.as_table() { let kv_pairs = match value.as_table() {
Some(table) => table, Some(table) => table,
@ -216,9 +234,25 @@ impl RocketConfig {
)) ))
}; };
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. // Set the environment configuration from the kv pairs.
config.set(env, &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) Ok(config)
} }
@ -320,7 +354,7 @@ mod test {
use std::env; use std::env;
use std::sync::Mutex; use std::sync::Mutex;
use super::{RocketConfig, ConfigError}; use super::{RocketConfig, ConfigError, GLOBAL_ENV_NAME};
use super::environment::{Environment, CONFIG_ENV}; use super::environment::{Environment, CONFIG_ENV};
use super::Environment::*; use super::Environment::*;
use super::config::Config; use super::config::Config;
@ -678,4 +712,42 @@ mod test {
session_key = "abcv" = other session_key = "abcv" = other
"#.to_string(), TEST_CONFIG_FILENAME).is_err()); "#.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
)
});
}
}
} }