From a4301c0a9a407ca8a7c85260d045ca199f4a7254 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 6 Jun 2020 23:22:04 -0700 Subject: [PATCH] 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. --- core/lib/src/config/config.rs | 91 ++++++++--- core/lib/src/config/error.rs | 8 +- core/lib/src/config/mod.rs | 297 +++++++++++++++------------------- core/lib/src/rocket.rs | 29 +++- 4 files changed, 232 insertions(+), 193 deletions(-) diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index 968e1817..f7d99160 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -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 { + 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>(path: P) -> Result { + 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

(env: Environment, path: P) -> Result where P: AsRef { - 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 { // 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` diff --git a/core/lib/src/config/error.rs b/core/lib/src/config/error.rs index e7041c82..8d548cba 100644 --- a/core/lib/src/config/error.rs +++ b/core/lib/src/config/error.rs @@ -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 diff --git a/core/lib/src/config/mod.rs b/core/lib/src/config/mod.rs index 8e2ff04f..c3540e91 100644 --- a/core/lib/src/config/mod.rs +++ b/core/lib/src/config/mod.rs @@ -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 = std::result::Result; -#[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, } -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 { - // Find the config file, starting from the `cwd` and working backwards. - let file = RocketConfig::find()?; - + pub fn read_from(path: &Path) -> Result { // 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 { - 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::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>>(filename: P) -> Result { + 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>(src: String, filename: P) -> Result { + fn parse(src: S, filename: P) -> Result + where S: Into, P: AsRef + { 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::() { 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::active_default() + fn active_default() -> Result { + 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)); } } diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index d22764b6..613d4756 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -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