Add 'Config::read()' and 'Config::read_from()'.

The former method allows constructing a 'Config' in the exact manner
Rocket does internally: reading the active environment's properties from
'Rocket.toml' and overriding values from environment variables. The
'read_from()' property does the same except it allows a custom config
file path.

This PR also improves the internal structure of the configuration code.
This commit is contained in:
Sergio Benitez 2020-06-06 23:22:04 -07:00
parent ea9865ec42
commit a4301c0a9a
4 changed files with 232 additions and 193 deletions

View File

@ -6,7 +6,7 @@ use std::fmt;
use crate::config::Environment::*;
use crate::config::{Result, ConfigBuilder, Environment, ConfigError, LoggingLevel};
use crate::config::{Table, Value, Array, Datetime};
use crate::config::{FullConfig, Table, Value, Array, Datetime};
use crate::http::private::Key;
use super::custom_values::*;
@ -75,8 +75,60 @@ macro_rules! config_from_raw {
}
impl Config {
/// Reads configuration values from the nearest `Rocket.toml`, searching for
/// a file by that name upwards from the current working directory to its
/// parents, as well as from environment variables named `ROCKET_{PARAM}` as
/// specified in the [`config`](crate::config) module documentation. Returns
/// the active configuration.
///
/// Specifically, this method:
///
/// 1. Locates the nearest `Rocket.toml` configuration file.
/// 2. Retrieves the active environment according to
/// [`Environment::active()`].
/// 3. Parses and validates the configuration file in its entirety,
/// extracting the values from the active environment.
/// 4. Overrides parameters with values set in `ROCKET_{PARAM}`
/// environment variables.
///
/// Any error encountered while performing these steps is returned.
///
/// # Example
///
/// ```rust
/// use rocket::config::Config;
///
/// # if false {
/// let config = Config::read().unwrap();
/// # }
/// ```
pub fn read() -> Result<Config> {
Self::read_from(&FullConfig::find()?)
}
/// This method is exactly like [`Config::read()`] except it uses the file
/// at `path` as the configuration file instead of searching for the nearest
/// `Rocket.toml`. The file must have the same format as `Rocket.toml`.
///
/// See [`Config::read()`] for more.
///
/// # Example
///
/// ```rust
/// use rocket::config::Config;
///
/// # if false {
/// let config = Config::read_from("/var/my-config.toml").unwrap();
/// # }
/// ```
pub fn read_from<P: AsRef<Path>>(path: P) -> Result<Config> {
FullConfig::read_from(path.as_ref()).map(FullConfig::take_active)
}
/// Returns a builder for `Config` structure where the default parameters
/// are set to those of `env`.
/// are set to those of `env`. This _does not_ read any configuration
/// parameters from any source. See [`config`](crate::config) for a list of
/// defaults.
///
/// # Example
///
@ -95,7 +147,12 @@ impl Config {
}
/// Returns a `Config` with the default parameters for the environment
/// `env`. See [`config`](crate::config) for a list of defaults.
/// `env`. This _does not_ read any configuration parameters from any
/// source. See [`config`](crate::config) for a list of defaults.
///
/// # Panics
///
/// Panics if randomness cannot be retrieved from the OS.
///
/// # Example
///
@ -106,11 +163,12 @@ impl Config {
/// my_config.set_port(1001);
/// ```
pub fn new(env: Environment) -> Config {
Config::default(env)
Config::default(env).expect("failed to read randomness from the OS")
}
/// Returns a `Config` with the default parameters of the active environment
/// as determined by the `ROCKET_ENV` environment variable.
/// as determined by the `ROCKET_ENV` environment variable. This _does not_
/// read any configuration parameters from any source.
///
/// If `ROCKET_ENV` is not set, the returned `Config` uses development
/// environment parameters when the application was compiled in `debug` mode
@ -182,19 +240,17 @@ impl Config {
}
/// Returns the default configuration for the environment `env` given that
/// the configuration was stored at `config_file_path`.
/// the configuration was stored at `path`. This doesn't read the file at
/// `path`; it simply uses `path` to set the config path property in the
/// returned `Config`.
///
/// # Error
///
/// Return a `BadFilePath` error if `path` does not have a parent.
///
/// # Panics
///
/// Panics if randomness cannot be retrieved from the OS.
pub(crate) fn default_from<P>(env: Environment, path: P) -> Result<Config>
where P: AsRef<Path>
{
let mut config = Config::default(env);
let mut config = Config::default(env)?;
let config_file_path = path.as_ref().to_path_buf();
if let Some(parent) = config_file_path.parent() {
@ -209,18 +265,15 @@ impl Config {
}
/// Returns the default configuration for the environment `env`.
///
/// # Panics
///
/// Panics if randomness cannot be retrieved from the OS.
pub(crate) fn default(env: Environment) -> Config {
pub(crate) fn default(env: Environment) -> Result<Config> {
// Note: This may truncate if num_cpus::get() / 2 > u16::max. That's okay.
let default_workers = (num_cpus::get() * 2) as u16;
// Use a generated secret key by default.
let key = SecretKey::Generated(Key::generate());
let new_key = Key::try_generate().ok_or(ConfigError::RandFailure)?;
let key = SecretKey::Generated(new_key);
match env {
Ok(match env {
Development => {
Config {
environment: Development,
@ -269,7 +322,7 @@ impl Config {
root_path: None,
}
}
}
})
}
/// Constructs a `BadType` error given the entry `name`, the invalid `val`

View File

@ -14,6 +14,8 @@ pub enum ConfigError {
NotFound,
/// There was an I/O error while reading the configuration file.
IoError,
/// There was an error retrieving randomness from the OS.
RandFailure,
/// There was an I/O error while setting a configuration parameter.
///
/// Parameters: (io_error, config_param_name)
@ -59,6 +61,7 @@ impl ConfigError {
match *self {
NotFound => error!("config file was not found"),
IoError => error!("failed reading the config file: IO error"),
RandFailure => error!("failed to read randomness from the OS"),
Io(ref error, param) => {
error!("I/O error while setting {}:", Paint::default(param).bold());
info_!("{}", error);
@ -135,6 +138,7 @@ impl fmt::Display for ConfigError {
match *self {
NotFound => write!(f, "config file was not found"),
IoError => write!(f, "I/O error while reading the config file"),
RandFailure => write!(f, "randomness could not be retrieved from the OS"),
Io(ref e, p) => write!(f, "I/O error while setting '{}': {}", p, e),
BadFilePath(ref p, _) => write!(f, "{:?} is not a valid config path", p),
BadEnv(ref e) => write!(f, "{:?} is not a valid `ROCKET_ENV` value", e),
@ -159,6 +163,7 @@ impl Error for ConfigError {
match *self {
NotFound => "config file was not found",
IoError => "there was an I/O error while reading the config file",
RandFailure => "randomness could not be retrieved from the OS",
Io(..) => "an I/O error occured while setting a configuration parameter",
BadFilePath(..) => "the config file path is invalid",
BadEntry(..) => "an environment specified as `[environment]` is invalid",
@ -177,6 +182,7 @@ impl PartialEq for ConfigError {
match (self, other) {
(&NotFound, &NotFound) => true,
(&IoError, &IoError) => true,
(&RandFailure, &RandFailure) => true,
(&Io(_, p1), &Io(_, p2)) => p1 == p2,
(&BadFilePath(ref p1, _), &BadFilePath(ref p2, _)) => p1 == p2,
(&BadEnv(ref e1), &BadEnv(ref e2)) => e1 == e2,
@ -190,7 +196,7 @@ impl PartialEq for ConfigError {
k1 == k2 && v1 == v2
}
(&Missing(ref k1), &Missing(ref k2)) => k1 == k2,
(&NotFound, _) | (&IoError, _) | (&Io(..), _)
(&NotFound, _) | (&IoError, _) | (&RandFailure, _) | (&Io(..), _)
| (&BadFilePath(..), _) | (&BadEnv(..), _) | (&ParseError(..), _)
| (&UnknownKey(..), _) | (&BadEntry(..), _) | (&BadType(..), _)
| (&BadEnvVal(..), _) | (&Missing(..), _) => false

View File

@ -192,7 +192,6 @@ use std::fs::{self, File};
use std::collections::HashMap;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process;
use std::env;
use toml;
@ -206,7 +205,6 @@ pub use self::builder::ConfigBuilder;
pub use crate::logger::LoggingLevel;
pub(crate) use self::toml_ext::LoggedValue;
use crate::logger;
use self::Environment::*;
use self::environment::CONFIG_ENV;
use crate::logger::COLORS_ENV;
@ -216,64 +214,60 @@ use crate::http::uncased::uncased_eq;
const CONFIG_FILENAME: &str = "Rocket.toml";
const GLOBAL_ENV_NAME: &str = "global";
const ENV_VAR_PREFIX: &str = "ROCKET_";
const PREHANDLED_VARS: [&str; 3] = ["ROCKET_CODEGEN_DEBUG", CONFIG_ENV, COLORS_ENV];
const CODEGEN_DEBUG_ENV: &str = "ROCKET_CODEGEN_DEBUG";
const PREHANDLED_VARS: [&str; 3] = [CODEGEN_DEBUG_ENV, CONFIG_ENV, COLORS_ENV];
/// Wraps `std::result` with the error type of [`ConfigError`].
pub type Result<T> = std::result::Result<T, ConfigError>;
#[doc(hidden)]
/// Stores a "full" config, which is all `Config`s for every environment.
#[derive(Debug, PartialEq)]
pub struct RocketConfig {
pub(crate) struct FullConfig {
pub active_env: Environment,
config: HashMap<Environment, Config>,
}
impl RocketConfig {
/// Read the configuration from the `Rocket.toml` file. The file is search
impl FullConfig {
/// Read the configuration from the `Rocket.toml` file. The file is searched
/// for recursively up the tree, starting from the CWD.
pub fn read() -> Result<RocketConfig> {
// Find the config file, starting from the `cwd` and working backwards.
let file = RocketConfig::find()?;
pub fn read_from(path: &Path) -> Result<FullConfig> {
// Try to open the config file for reading.
let mut handle = File::open(&file).map_err(|_| ConfigError::IoError)?;
let mut handle = File::open(path).map_err(|_| ConfigError::IoError)?;
// Read the configure file to a string for parsing.
let mut contents = String::new();
handle.read_to_string(&mut contents).map_err(|_| ConfigError::IoError)?;
// Parse the config and return the result.
RocketConfig::parse(contents, &file)
}
let mut config = FullConfig::parse(contents, path)?;
/// Return the default configuration for all environments and marks the
/// active environment (via the CONFIG_ENV variable) as active.
pub fn active_default_from(filename: Option<&Path>) -> Result<RocketConfig> {
let mut defaults = HashMap::new();
if let Some(path) = filename {
defaults.insert(Development, Config::default_from(Development, &path)?);
defaults.insert(Staging, Config::default_from(Staging, &path)?);
defaults.insert(Production, Config::default_from(Production, &path)?);
} else {
defaults.insert(Development, Config::default(Development));
defaults.insert(Staging, Config::default(Staging));
defaults.insert(Production, Config::default(Production));
}
let mut config = RocketConfig {
active_env: Environment::active()?,
config: defaults,
};
// Override any variables from the environment.
// Override any config values with those from the environment.
config.override_from_env()?;
Ok(config)
}
/// Return the default configuration for all environments and marks the
/// active environment (via the CONFIG_ENV variable) as active.
pub fn active_default() -> Result<RocketConfig> {
RocketConfig::active_default_from(None)
/// active environment (from `CONFIG_ENV`) as active. This doesn't read
/// `filename`, nor any other config values from any source; it simply uses
/// `filename` to set up the config path property in the returned `Config`.
pub fn active_default<'a, P: Into<Option<&'a Path>>>(filename: P) -> Result<FullConfig> {
let mut defaults = HashMap::new();
if let Some(path) = filename.into() {
defaults.insert(Development, Config::default_from(Development, &path)?);
defaults.insert(Staging, Config::default_from(Staging, &path)?);
defaults.insert(Production, Config::default_from(Production, &path)?);
} else {
defaults.insert(Development, Config::default(Development)?);
defaults.insert(Staging, Config::default(Staging)?);
defaults.insert(Production, Config::default(Production)?);
}
Ok(FullConfig {
active_env: Environment::active()?,
config: defaults,
})
}
/// Iteratively search for `CONFIG_FILENAME` starting at the current working
@ -320,6 +314,7 @@ impl RocketConfig {
}
/// Retrieves the `Config` for the environment `env`.
#[cfg(test)]
pub fn get(&self, env: Environment) -> &Config {
match self.config.get(&env) {
Some(config) => config,
@ -328,11 +323,16 @@ impl RocketConfig {
}
/// Retrieves the `Config` for the active environment.
#[inline]
#[cfg(test)]
pub fn active(&self) -> &Config {
self.get(self.active_env)
}
/// Retrieves the `Config` for the active environment.
pub fn take_active(mut self) -> Config {
self.config.remove(&self.active_env).expect("missing active config")
}
// Override all environments with values from env variables if present.
fn override_from_env(&mut self) -> Result<()> {
for (key, val) in env::vars() {
@ -371,10 +371,13 @@ impl RocketConfig {
/// Parses the configuration from the Rocket.toml file. Also overrides any
/// values there with values from the environment.
fn parse<P: AsRef<Path>>(src: String, filename: P) -> Result<RocketConfig> {
fn parse<S, P>(src: S, filename: P) -> Result<FullConfig>
where S: Into<String>, P: AsRef<Path>
{
use self::ConfigError::ParseError;
// Parse the source as TOML, if possible.
let src = src.into();
let path = filename.as_ref().to_path_buf();
let table = match src.parse::<toml::Value>() {
Ok(toml::Value::Table(table)) => table,
@ -386,7 +389,7 @@ impl RocketConfig {
};
// Create a config with the defaults; set the env to the active one.
let mut config = RocketConfig::active_default_from(Some(filename.as_ref()))?;
let mut config = FullConfig::active_default(filename.as_ref())?;
// Store all of the global overrides, if any, for later use.
let mut global = None;
@ -422,57 +425,16 @@ impl RocketConfig {
}
}
// Override any variables from the environment.
config.override_from_env()?;
Ok(config)
}
}
/// Returns the active configuration and whether this call initialized the
/// configuration. The configuration can only be initialized once.
///
/// Initializes the global RocketConfig by reading the Rocket config file from
/// the current directory or any of its parents. Returns the active
/// configuration, which is determined by the config env variable. If there as a
/// problem parsing the configuration, the error is printed and the program is
/// aborted. If there an I/O issue reading the config file, a warning is printed
/// and the default configuration is used. If there is no config file, the
/// default configuration is used.
///
/// # Panics
///
/// If there is a problem, prints a nice error message and bails.
pub(crate) fn init() -> Config {
let bail = |e: ConfigError| -> ! {
logger::init(LoggingLevel::Debug);
e.pretty_print();
process::exit(1)
};
use self::ConfigError::*;
let config = RocketConfig::read().unwrap_or_else(|e| {
match e {
| ParseError(..) | BadEntry(..) | BadEnv(..) | BadType(..) | Io(..)
| BadFilePath(..) | BadEnvVal(..) | UnknownKey(..)
| Missing(..) => bail(e),
IoError => warn!("Failed reading Rocket.toml. Using defaults."),
NotFound => { /* try using the default below */ }
}
RocketConfig::active_default().unwrap_or_else(|e| bail(e))
});
// FIXME: Should probably store all of the config.
config.active().clone()
}
#[cfg(test)]
mod test {
use std::env;
use std::sync::Mutex;
use super::{RocketConfig, Config, ConfigError, ConfigBuilder};
use super::{FullConfig, Config, ConfigError, ConfigBuilder};
use super::{Environment, GLOBAL_ENV_NAME};
use super::environment::CONFIG_ENV;
use super::Environment::*;
@ -505,8 +467,10 @@ mod test {
);
}
fn active_default() -> Result<RocketConfig> {
RocketConfig::active_default()
fn active_default() -> Result<FullConfig> {
let mut config = FullConfig::active_default(None)?;
config.override_from_env()?;
Ok(config)
}
fn default_config(env: Environment) -> ConfigBuilder {
@ -560,7 +524,7 @@ mod test {
let toml_table = format!("[{}]\n", env);
let e_str = env.to_string();
let err = ConfigError::BadEntry(e_str, TEST_CONFIG_FILENAME.into());
assert!(RocketConfig::parse(toml_table, TEST_CONFIG_FILENAME)
assert!(FullConfig::parse(toml_table, TEST_CONFIG_FILENAME)
.err().map_or(false, |e| e == err));
}
}
@ -596,21 +560,21 @@ mod test {
expected.environment = Development;
let dev_config = ["[dev]", config_str].join("\n");
let parsed = RocketConfig::parse(dev_config, TEST_CONFIG_FILENAME);
let parsed = FullConfig::parse(dev_config, TEST_CONFIG_FILENAME);
check_config!(Development, parsed, expected.clone());
check_config!(Staging, parsed, default_config(Staging));
check_config!(Production, parsed, default_config(Production));
expected.environment = Staging;
let stage_config = ["[stage]", config_str].join("\n");
let parsed = RocketConfig::parse(stage_config, TEST_CONFIG_FILENAME);
let parsed = FullConfig::parse(stage_config, TEST_CONFIG_FILENAME);
check_config!(Staging, parsed, expected.clone());
check_config!(Development, parsed, default_config(Development));
check_config!(Production, parsed, default_config(Production));
expected.environment = Production;
let prod_config = ["[prod]", config_str].join("\n");
let parsed = RocketConfig::parse(prod_config, TEST_CONFIG_FILENAME);
let parsed = FullConfig::parse(prod_config, TEST_CONFIG_FILENAME);
check_config!(Production, parsed, expected);
check_config!(Development, parsed, default_config(Development));
check_config!(Staging, parsed, default_config(Staging));
@ -622,35 +586,35 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "dev");
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[development]
address = "localhost"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Development).address("localhost")
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[development]
address = "127.0.0.1"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Development).address("127.0.0.1")
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[development]
address = "::"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Development).address("::")
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[dev]
address = "2001:db8::370:7334"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Development).address("2001:db8::370:7334")
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[dev]
address = "0.0.0.0"
"#.to_string(), TEST_CONFIG_FILENAME), {
@ -664,22 +628,22 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
address = 0000
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
address = true
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
address = "........"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
address = "1.2.3.4:100"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
@ -693,24 +657,24 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "dev");
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
tls = { certs = "some/path.pem", key = "some/key.pem" }
"#.to_string(), TEST_CONFIG_FILENAME).is_ok());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging.tls]
certs = "some/path.pem"
key = "some/key.pem"
"#.to_string(), TEST_CONFIG_FILENAME).is_ok());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[global.tls]
certs = "some/path.pem"
key = "some/key.pem"
"#.to_string(), TEST_CONFIG_FILENAME).is_ok());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[global]
tls = { certs = "some/path.pem", key = "some/key.pem" }
"#.to_string(), TEST_CONFIG_FILENAME).is_ok());
@ -722,22 +686,22 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
tls = "hello"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
tls = { certs = "some/path.pem" }
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
tls = { certs = "some/path.pem", key = "some/key.pem", extra = "bah" }
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
tls = { cert = "some/path.pem", key = "some/key.pem" }
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
@ -749,21 +713,21 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "stage");
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
port = 100
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).port(100)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
port = 6000
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).port(6000)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
port = 65535
"#.to_string(), TEST_CONFIG_FILENAME), {
@ -777,27 +741,27 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
port = true
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[production]
port = "hello"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
port = -1
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
port = 65536
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
port = 105836
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
@ -809,21 +773,21 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "stage");
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
workers = 1
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).workers(1)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
workers = 300
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).workers(300)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
workers = 65535
"#.to_string(), TEST_CONFIG_FILENAME), {
@ -837,27 +801,27 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
workers = true
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[production]
workers = "hello"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
workers = -1
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
workers = 65536
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[staging]
workers = 105836
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
@ -869,28 +833,28 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "stage");
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
keep_alive = 10
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(10)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
keep_alive = 0
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(0)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
keep_alive = 348
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(348)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
keep_alive = 0
"#.to_string(), TEST_CONFIG_FILENAME), {
@ -904,22 +868,22 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
keep_alive = true
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
keep_alive = -10
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
keep_alive = "Some(10)"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
keep_alive = 4294967296
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
@ -931,7 +895,7 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "stage");
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
log = "normal"
"#.to_string(), TEST_CONFIG_FILENAME), {
@ -939,21 +903,21 @@ mod test {
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
log = "debug"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).log_level(LoggingLevel::Debug)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
log = "critical"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).log_level(LoggingLevel::Critical)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
log = "off"
"#.to_string(), TEST_CONFIG_FILENAME), {
@ -967,17 +931,17 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
log = false
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[development]
log = 0
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[prod]
log = "no"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
@ -989,7 +953,7 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "stage");
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
secret_key = "TpUiXK2d/v5DFxJnWL12suJKPExKR8h9zd/o+E7SU+0="
"#.to_string(), TEST_CONFIG_FILENAME), {
@ -998,7 +962,7 @@ mod test {
)
});
check_config!(RocketConfig::parse(r#"
check_config!(FullConfig::parse(r#"
[stage]
secret_key = "jTyprDberFUiUFsJ3vcb1XKsYHWNBRvWAnXTlbTgGFU="
"#.to_string(), TEST_CONFIG_FILENAME), {
@ -1014,17 +978,17 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
secret_key = true
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
secret_key = 1283724897238945234897
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
secret_key = "abcv"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
@ -1036,16 +1000,16 @@ mod test {
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
1. = 2
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
assert!(FullConfig::parse(r#"
[dev]
secret_key = "abcv" = other
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
@ -1060,21 +1024,21 @@ mod test {
for env in &Environment::ALL {
env::set_var(CONFIG_ENV, env.to_string());
check_config!(RocketConfig::parse(format!(r#"
check_config!(FullConfig::parse(format!(r#"
[{}]
address = "::1"
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
default_config(*env).address("::1")
});
check_config!(RocketConfig::parse(format!(r#"
check_config!(FullConfig::parse(format!(r#"
[{}]
database = "mysql"
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
default_config(*env).extra("database", "mysql")
});
check_config!(RocketConfig::parse(format!(r#"
check_config!(FullConfig::parse(format!(r#"
[{}]
port = 3980
"#, GLOBAL_ENV_NAME), TEST_CONFIG_FILENAME), {
@ -1083,6 +1047,19 @@ mod test {
}
}
macro_rules! check_value {
($key:expr, $val:expr, $config:expr) => (
match $key {
"log" => assert_eq!($config.log_level, $val.parse().unwrap()),
"port" => assert_eq!($config.port, $val.parse().unwrap()),
"address" => assert_eq!($config.address, $val),
"extra_extra" => assert_eq!($config.get_bool($key).unwrap(), true),
"workers" => assert_eq!($config.workers, $val.parse().unwrap()),
_ => panic!("Unexpected key: {}", $key)
}
)
}
#[test]
fn test_env_override() {
// Take the lock so changing the environment doesn't cause races.
@ -1093,33 +1070,22 @@ mod test {
("address", "1.2.3.4"), ("EXTRA_EXTRA", "true"), ("workers", "3")
];
let check_value = |key: &str, val: &str, config: &Config| {
match key {
"log" => assert_eq!(config.log_level, val.parse().unwrap()),
"port" => assert_eq!(config.port, val.parse().unwrap()),
"address" => assert_eq!(config.address, val),
"extra_extra" => assert_eq!(config.get_bool(key).unwrap(), true),
"workers" => assert_eq!(config.workers, val.parse().unwrap()),
_ => panic!("Unexpected key: {}", key)
}
};
// Check that setting the environment variable actually changes the
// config for the default active and nonactive environments.
for &(key, val) in &pairs {
env::set_var(format!("ROCKET_{}", key), val);
let rconfig = active_default().unwrap();
// Check that it overrides the active config.
for env in &Environment::ALL {
env::set_var(CONFIG_ENV, env.to_string());
let rconfig = active_default().unwrap();
check_value(&*key.to_lowercase(), val, rconfig.active());
check_value!(&*key.to_lowercase(), val, rconfig.active());
}
// And non-active configs.
let rconfig = active_default().unwrap();
for env in &Environment::ALL {
check_value(&*key.to_lowercase(), val, rconfig.get(*env));
check_value!(&*key.to_lowercase(), val, rconfig.get(*env));
}
}
@ -1145,19 +1111,20 @@ mod test {
port = 7810
workers = 21
log = "normal"
"#.to_string();
"#;
// Check that setting the environment variable actually changes the
// config for the default active environments.
for &(key, val) in &pairs {
env::set_var(format!("ROCKET_{}", key), val);
let r = RocketConfig::parse(toml.clone(), TEST_CONFIG_FILENAME).unwrap();
check_value(&*key.to_lowercase(), val, r.active());
let mut r = FullConfig::parse(toml, TEST_CONFIG_FILENAME).unwrap();
r.override_from_env().unwrap();
check_value!(&*key.to_lowercase(), val, r.active());
// And non-active configs.
for env in &Environment::ALL {
check_value(&*key.to_lowercase(), val, r.get(*env));
check_value!(&*key.to_lowercase(), val, r.get(*env));
}
}

View File

@ -12,7 +12,7 @@ use state::Container;
use crate::{logger, handler};
use crate::ext::ReadExt;
use crate::config::{self, Config, LoggedValue};
use crate::config::{Config, FullConfig, ConfigError, LoggedValue};
use crate::request::{Request, FormItems};
use crate::data::Data;
use crate::response::{Body, Response};
@ -344,16 +344,17 @@ impl Rocket {
/// Create a new `Rocket` application using the configuration information in
/// `Rocket.toml`. If the file does not exist or if there is an I/O error
/// reading the file, the defaults are used. See the [`config`]
/// documentation for more information on defaults.
/// reading the file, the defaults are used. See the
/// [`config`](crate::config) documentation for more information on
/// defaults.
///
/// This method is typically called through the
/// [`rocket::ignite()`](crate::ignite) alias.
///
/// # Panics
///
/// If there is an error parsing the `Rocket.toml` file, this functions
/// prints a nice error message and then exits the process.
/// If there is an error reading configuration sources, this function prints
/// a nice error message and then exits the process.
///
/// # Examples
///
@ -362,10 +363,22 @@ impl Rocket {
/// rocket::ignite()
/// # };
/// ```
#[inline]
pub fn ignite() -> Rocket {
// Note: init() will exit the process under config errors.
Rocket::configured(config::init())
Config::read()
.or_else(|e| match e {
ConfigError::IoError => {
warn!("Failed to read 'Rocket.toml'. Using defaults.");
Ok(FullConfig::active_default(None)?.take_active())
}
ConfigError::NotFound => Ok(FullConfig::active_default(None)?.take_active()),
_ => Err(e)
})
.map(Rocket::configured)
.unwrap_or_else(|e: ConfigError| {
logger::init(logger::LoggingLevel::Debug);
e.pretty_print();
std::process::exit(1)
})
}
/// Creates a new `Rocket` application using the supplied custom