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]
template_dir = "static"
[stage]
template_dir = "static"
[prod]
[global]
template_dir = "static"

View File

@ -21,7 +21,7 @@ pub struct Config {
/// The environment that this configuration corresponds to.
pub env: Environment,
session_key: RwLock<Option<String>>,
extra: HashMap<String, Value>,
extras: HashMap<String, Value>,
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<Item=(&'a String, &'a Value)> {
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<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")
}
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")
}
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")
}
@ -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
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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<RocketConfig> = 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<T> = ::std::result::Result<T, ConfigError>;
@ -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
)
});
}
}
}