diff --git a/contrib/src/templates/mod.rs b/contrib/src/templates/mod.rs index 4f8d068a..a8b9e042 100644 --- a/contrib/src/templates/mod.rs +++ b/contrib/src/templates/mod.rs @@ -16,7 +16,7 @@ use self::glob::glob; use std::path::{Path, PathBuf}; use std::collections::HashMap; -use rocket::Rocket; +use rocket::config; use rocket::response::{Content, ResponseOutcome, Responder}; use rocket::http::hyper::FreshHyperResponse; use rocket::http::{ContentType, StatusCode}; @@ -114,11 +114,22 @@ pub struct TemplateInfo { data_type: Option } +const DEFAULT_TEMPLATE_DIR: &'static str = "templates"; + lazy_static! { static ref TEMPLATES: HashMap = discover_templates(); - // FIXME: Ensure template_dir is an absolute path starting at crate root. - static ref TEMPLATE_DIR: String = - Rocket::config("template_dir").unwrap_or("templates").to_string(); + static ref TEMPLATE_DIR: String = { + config::active().map(|config| { + let dir = config.get_str("template_dir").map_err(|e| { + if !e.is_not_found() { + e.pretty_print(); + warn_!("Using default directory '{}'", DEFAULT_TEMPLATE_DIR); + } + }).unwrap_or(DEFAULT_TEMPLATE_DIR); + + config.root().join(dir).to_string_lossy().into_owned() + }).unwrap_or("templates".to_string()) + }; } impl Template { @@ -133,6 +144,7 @@ impl Template { let template = TEMPLATES.get(name); if template.is_none() { error_!("Template '{}' does not exist.", name); + info_!("Searched in '{}'.", *TEMPLATE_DIR); return Template(None, None); } diff --git a/examples/config/Rocket.toml b/examples/config/Rocket.toml index d7ec8ea9..d644f6f8 100644 --- a/examples/config/Rocket.toml +++ b/examples/config/Rocket.toml @@ -5,6 +5,8 @@ address = "localhost" port = 8000 log = "normal" +hi = "Hello!" +is_extra = true [staging] address = "0.0.0.0" diff --git a/examples/todo/Rocket.toml b/examples/todo/Rocket.toml new file mode 100644 index 00000000..0e7811f9 --- /dev/null +++ b/examples/todo/Rocket.toml @@ -0,0 +1,12 @@ +# TODO: Allow a `global` pseudo-environment that overrides all environments. +# [global] +# template_dir = "static" + +[dev] +template_dir = "static" + +[stage] +template_dir = "static" + +[prod] +template_dir = "static" diff --git a/examples/todo/templates/index.html.tera b/examples/todo/static/index.html.tera similarity index 100% rename from examples/todo/templates/index.html.tera rename to examples/todo/static/index.html.tera diff --git a/lib/src/config/config.rs b/lib/src/config/config.rs index b757ed33..fc493e01 100644 --- a/lib/src/config/config.rs +++ b/lib/src/config/config.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use std::net::ToSocketAddrs; +use std::path::Path; -use super::Environment::*; -use super::Environment; +use config::Environment::*; +use config::{self, Environment, ConfigError}; use logger::LoggingLevel; use toml::Value; @@ -13,28 +14,28 @@ pub struct Config { pub port: usize, pub log_level: LoggingLevel, pub session_key: Option, - pub extra: HashMap, + pub env: Environment, + extra: HashMap, + filename: String, } macro_rules! parse { - ($val:expr, as_str) => ( - match $val.as_str() { - Some(v) => v, - None => return Err("a string") - } - ); - - ($val:expr, as_integer) => ( - match $val.as_integer() { - Some(v) => v, - None => return Err("an integer") - } + ($conf:expr, $name:expr, $val:expr, $method:ident, $expect: expr) => ( + $val.$method().ok_or_else(|| { + $conf.bad_type($name, $val, $expect) + }) ); } impl Config { - pub fn default_for(env: Environment) -> Config { - match env { + pub fn default_for(env: Environment, filename: &str) -> config::Result { + let file_path = Path::new(filename); + if file_path.parent().is_none() { + return Err(ConfigError::BadFilePath(filename.to_string(), + "Configuration files must be rooted in a directory.")); + } + + Ok(match env { Development => { Config { address: "localhost".to_string(), @@ -42,6 +43,8 @@ impl Config { log_level: LoggingLevel::Normal, session_key: None, extra: HashMap::new(), + env: env, + filename: filename.to_string(), } } Staging => { @@ -51,6 +54,8 @@ impl Config { log_level: LoggingLevel::Normal, session_key: None, extra: HashMap::new(), + env: env, + filename: filename.to_string(), } } Production => { @@ -60,44 +65,118 @@ impl Config { log_level: LoggingLevel::Critical, session_key: None, extra: HashMap::new(), + env: env, + filename: filename.to_string(), } } - } + }) } - pub fn set(&mut self, name: &str, value: &Value) -> Result<(), &'static str> { + #[inline(always)] + fn bad_type(&self, name: &str, val: &Value, expect: &'static str) -> ConfigError { + let id = format!("{}.{}", self.env, name); + ConfigError::BadType(id, expect, val.type_str(), self.filename.clone()) + } + + pub fn set(&mut self, name: &str, val: &Value) -> config::Result<()> { if name == "address" { - let address_str = parse!(value, as_str).to_string(); + let address_str = parse!(self, name, val, as_str, "a string")?; if address_str.contains(":") { - return Err("an IP address with no port") + return Err(self.bad_type(name, val, "an IP address with no port")); } else if format!("{}:{}", address_str, 80).to_socket_addrs().is_err() { - return Err("a valid IP address") + return Err(self.bad_type(name, val, "a valid IP address")); } - self.address = address_str; + self.address = address_str.to_string(); } else if name == "port" { - let port = parse!(value, as_integer); + let port = parse!(self, name, val, as_integer, "an integer")?; if port < 0 { - return Err("an unsigned integer"); + return Err(self.bad_type(name, val, "an unsigned integer")); } self.port = port as usize; } else if name == "session_key" { - let key = parse!(value, as_str); + let key = parse!(self, name, val, as_str, "a string")?; if key.len() != 32 { - return Err("a 192-bit base64 encoded string") + return Err(self.bad_type(name, val, "a 192-bit base64 string")); } self.session_key = Some(key.to_string()); } else if name == "log" { - self.log_level = match parse!(value, as_str).parse() { + let level_str = parse!(self, name, val, as_str, "a string")?; + self.log_level = match level_str.parse() { Ok(level) => level, - Err(_) => return Err("log level ('normal', 'critical', 'debug')"), + Err(_) => return Err(self.bad_type(name, val, + "log level ('normal', 'critical', 'debug')")) }; } else { - self.extra.insert(name.into(), value.clone()); + self.extra.insert(name.into(), val.clone()); } Ok(()) } + + pub fn get_str<'a>(&'a self, name: &str) -> config::Result<&'a str> { + let value = self.extra.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)?; + 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)?; + 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)?; + parse!(self, name, value, as_float, "a float") + } + + pub fn root(&self) -> &Path { + match Path::new(self.filename.as_str()).parent() { + Some(parent) => &parent, + None => panic!("root(): filename {} has no parent", self.filename) + } + } + + #[inline(always)] + pub fn extras<'a>(&'a self) -> impl Iterator { + self.extra.iter() + } + + // Builder pattern below, mostly for testing. + + #[inline(always)] + pub fn address(mut self, var: String) -> Self { + self.address = var; + self + } + + #[inline(always)] + pub fn port(mut self, var: usize) -> Self { + self.port = var; + self + } + + #[inline(always)] + pub fn log_level(mut self, var: LoggingLevel) -> Self { + self.log_level = var; + self + } + + #[inline(always)] + pub fn session_key(mut self, var: String) -> Self { + self.session_key = Some(var); + self + } + + #[inline(always)] + pub fn env(mut self, var: Environment) -> Self { + self.env = var; + self + } } diff --git a/lib/src/config/error.rs b/lib/src/config/error.rs index 19bea1b6..9882bc8d 100644 --- a/lib/src/config/error.rs +++ b/lib/src/config/error.rs @@ -16,6 +16,8 @@ pub enum ConfigError { BadCWD, NotFound, IOError, + /// (path, reason) + BadFilePath(String, &'static str), /// (environment_name) BadEnv(String), /// (environment_name, filename) @@ -35,6 +37,10 @@ impl ConfigError { BadCWD => error!("couldn't get current working directory"), NotFound => error!("config file was not found"), IOError => error!("failed reading the config file: IO error"), + BadFilePath(ref path, ref reason) => { + error!("configuration file path '{}' is invalid", path); + info_!("{}", reason); + } BadEntry(ref name, ref filename) => { error!("[{}] is not a known configuration environment", name); info_!("in {}", White.paint(filename)); @@ -63,4 +69,12 @@ impl ConfigError { } } } + + pub fn is_not_found(&self) -> bool { + use self::ConfigError::*; + match *self { + NotFound => true, + _ => false + } + } } diff --git a/lib/src/config/mod.rs b/lib/src/config/mod.rs index 960e4e81..027a9784 100644 --- a/lib/src/config/mod.rs +++ b/lib/src/config/mod.rs @@ -17,8 +17,12 @@ use self::config::Config; use toml::{self, Table}; use logger::{self, LoggingLevel}; +static mut CONFIG: Option = None; + const CONFIG_FILENAME: &'static str = "Rocket.toml"; +pub type Result = ::std::result::Result; + #[derive(Debug, PartialEq, Clone)] pub struct RocketConfig { pub active_env: Environment, @@ -28,7 +32,7 @@ pub struct RocketConfig { impl RocketConfig { /// Iteratively search for `file` in `pwd` and its parents, returning the path /// to the file or an Error::NoKey if the file couldn't be found. - fn find() -> Result { + fn find() -> Result { let cwd = env::current_dir().map_err(|_| ConfigError::BadCWD)?; let mut current = cwd.as_path(); @@ -47,26 +51,24 @@ impl RocketConfig { Err(ConfigError::NotFound) } - fn set(&mut self, env: Environment, kvs: &Table, filename: &str) - -> Result<(), ConfigError> { - let config = self.config.entry(env).or_insert(Config::default_for(env)); + fn set(&mut self, env: Environment, kvs: &Table) + -> Result<()> { + let config = match self.config.get_mut(&env) { + Some(config) => config, + None => panic!("set(): {} config is missing.", env), + }; + for (key, value) in kvs { - if let Err(expected) = config.set(key, value) { - let name = format!("{}.{}", env, key); - return Err(ConfigError::BadType( - name, expected, value.type_str(), filename.to_string() - )) - } + config.set(key, value)?; } Ok(()) } pub fn get(&self, env: Environment) -> &Config { - if let Some(config) = self.config.get(&env) { - config - } else { - panic!("No value from environment: {:?}", env); + match self.config.get(&env) { + Some(config) => config, + None => panic!("get(): {} config is missing.", env), } } @@ -74,7 +76,7 @@ impl RocketConfig { self.get(self.active_env) } - fn parse(src: String, filename: &str) -> Result { + fn parse(src: String, filename: &str) -> Result { // Parse the source as TOML, if possible. let mut parser = toml::Parser::new(&src); let toml = parser.parse().ok_or(ConfigError::ParseError( @@ -88,7 +90,7 @@ impl RocketConfig { ))?; // Create a config with the defaults, but the set the env to the active - let mut config = RocketConfig::active_default()?; + let mut config = RocketConfig::active_default(filename)?; // Parse the values from the TOML file. for (entry, value) in toml { @@ -106,13 +108,13 @@ impl RocketConfig { }; // Set the environment configuration from the kv pairs. - config.set(env, &kv_pairs, filename)?; + config.set(env, &kv_pairs)?; } Ok(config) } - pub fn read() -> Result { + pub fn read() -> Result { // Find the config file, starting from the `cwd` and working backwords. let file = RocketConfig::find()?; @@ -127,34 +129,39 @@ impl RocketConfig { RocketConfig::parse(contents, &file.to_string_lossy()) } - pub fn active_default() -> Result { - let mut default = RocketConfig::default(); - default.active_env = Environment::active()?; - Ok(default) + pub fn active_default(filename: &str) -> Result { + let mut defaults = HashMap::new(); + defaults.insert(Development, Config::default_for(Development, filename)?); + defaults.insert(Staging, Config::default_for(Staging, filename)?); + defaults.insert(Production, Config::default_for(Production, filename)?); + + Ok(RocketConfig { + active_env: Environment::active()?, + config: defaults, + }) } } -impl Default for RocketConfig { - fn default() -> RocketConfig { - RocketConfig { - active_env: Environment::Development, - config: { - let mut default_config = HashMap::new(); - default_config.insert(Development, Config::default_for(Development)); - default_config.insert(Staging, Config::default_for(Staging)); - default_config.insert(Production, Config::default_for(Production)); - default_config - }, - } +/// 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 progam 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. +/// +/// # Thread-Safety +/// +/// This function is _not_ thread-safe. It should be called by a single thread. +pub fn init() -> &'static Config { + if let Some(config) = active() { + return config; } -} -/// Read the Rocket config file from the current directory or any of its -/// parents. If there is no such file, return the default config. A returned -/// config will have its active environment set to whatever was passed in with -/// the rocket config env variable. If there is a problem doing any of this, -/// print a nice error message and bail. -pub fn read_or_default() -> RocketConfig { let bail = |e: ConfigError| -> ! { logger::init(LoggingLevel::Debug); e.pretty_print(); @@ -162,40 +169,54 @@ pub fn read_or_default() -> RocketConfig { }; use self::ConfigError::*; - RocketConfig::read().unwrap_or_else(|e| { + let config = RocketConfig::read().unwrap_or_else(|e| { match e { - ParseError(..) | BadEntry(..) | BadEnv(..) | BadType(..) => bail(e), - IOError | BadCWD => warn!("failed reading Rocket.toml. using defaults"), + ParseError(..) | BadEntry(..) | BadEnv(..) | BadType(..) + | BadFilePath(..) => bail(e), + IOError | BadCWD => warn!("Failed reading Rocket.toml. Using defaults."), NotFound => { /* try using the default below */ } } - RocketConfig::active_default().unwrap_or_else(|e| bail(e)) - }) + let default_path = match env::current_dir() { + Ok(path) => path.join(&format!(".{}.{}", "default", CONFIG_FILENAME)), + Err(_) => bail(ConfigError::BadCWD) + }; + + let filename = default_path.to_string_lossy(); + RocketConfig::active_default(&filename).unwrap_or_else(|e| bail(e)) + }); + + unsafe { + CONFIG = Some(config); + CONFIG.as_ref().unwrap().active() + } +} + +pub fn active() -> Option<&'static Config> { + unsafe { CONFIG.as_ref().map(|c| c.active()) } } #[cfg(test)] mod test { use std::env; - use std::collections::HashMap; use std::sync::Mutex; - use super::{RocketConfig, CONFIG_FILENAME, ConfigError}; - use super::environment::CONFIG_ENV; + use super::{RocketConfig, ConfigError}; + use super::environment::{Environment, CONFIG_ENV}; use super::Environment::*; use super::config::Config; + use super::Result; use ::toml::Value; use ::logger::LoggingLevel; + const TEST_CONFIG_FILENAME: &'static str = "/tmp/testing/Rocket.toml"; + lazy_static! { static ref ENV_LOCK: Mutex = Mutex::new(0); } macro_rules! check_config { - ($rconfig:expr => { $($param:tt)+ }) => ( - check_config!($rconfig, Config { $($param)+ }) - ); - ($rconfig:expr, $econfig:expr) => ( match $rconfig { Ok(config) => assert_eq!(config.active(), &$econfig), @@ -211,6 +232,14 @@ mod test { ); } + fn active_default() -> Result { + RocketConfig::active_default(TEST_CONFIG_FILENAME) + } + + fn default_config(env: Environment) -> Config { + Config::default_for(env, TEST_CONFIG_FILENAME).expect("config") + } + #[test] fn test_defaults() { // Take the lock so changing the environment doesn't cause races. @@ -218,24 +247,24 @@ mod test { // First, without an environment. Should get development defaults. env::remove_var(CONFIG_ENV); - check_config!(RocketConfig::active_default(), Config::default_for(Development)); + check_config!(active_default(), default_config(Development)); // Now with an explicit dev environment. for env in &["development", "dev"] { env::set_var(CONFIG_ENV, env); - check_config!(RocketConfig::active_default(), Config::default_for(Development)); + check_config!(active_default(), default_config(Development)); } // Now staging. for env in &["stage", "staging"] { env::set_var(CONFIG_ENV, env); - check_config!(RocketConfig::active_default(), Config::default_for(Staging)); + check_config!(active_default(), default_config(Staging)); } // Finally, production. for env in &["prod", "production"] { env::set_var(CONFIG_ENV, env); - check_config!(RocketConfig::active_default(), Config::default_for(Production)); + check_config!(active_default(), default_config(Production)); } } @@ -247,15 +276,16 @@ mod test { for env in &["", "p", "pr", "pro", "prodo", " prod", "dev ", "!dev!", "🚀 "] { env::set_var(CONFIG_ENV, env); let err = ConfigError::BadEnv(env.to_string()); - assert!(RocketConfig::active_default().err().map_or(false, |e| e == err)); + assert!(active_default().err().map_or(false, |e| e == err)); } // Test that a bunch of invalid environment names give the right error. env::remove_var(CONFIG_ENV); for env in &["p", "pr", "pro", "prodo", "bad", "meow", "this", "that"] { let toml_table = format!("[{}]\n", env); - let err = ConfigError::BadEntry(env.to_string(), CONFIG_FILENAME.into()); - assert!(RocketConfig::parse(toml_table, CONFIG_FILENAME) + let e_str = env.to_string(); + let err = ConfigError::BadEntry(e_str, TEST_CONFIG_FILENAME.into()); + assert!(RocketConfig::parse(toml_table, TEST_CONFIG_FILENAME) .err().map_or(false, |e| e == err)); } } @@ -276,36 +306,35 @@ mod test { pi = 3.14 "#; - let mut extra: HashMap = HashMap::new(); - extra.insert("template_dir".to_string(), Value::String("mine".into())); - extra.insert("json".to_string(), Value::Boolean(true)); - extra.insert("pi".to_string(), Value::Float(3.14)); - - let expected = Config { - address: "1.2.3.4".to_string(), - port: 7810, - log_level: LoggingLevel::Critical, - session_key: Some("01234567890123456789012345678901".to_string()), - extra: extra - }; + let mut expected = default_config(Development); + expected.address = "1.2.3.4".to_string(); + expected.port = 7810; + expected.log_level = LoggingLevel::Critical; + expected.session_key = Some("01234567890123456789012345678901".to_string()); + expected.set("template_dir", &Value::String("mine".into())).unwrap(); + expected.set("json", &Value::Boolean(true)).unwrap(); + expected.set("pi", &Value::Float(3.14)).unwrap(); + expected.env = Development; let dev_config = ["[dev]", config_str].join("\n"); - let parsed = RocketConfig::parse(dev_config, CONFIG_FILENAME); + let parsed = RocketConfig::parse(dev_config, TEST_CONFIG_FILENAME); check_config!(Development, parsed, expected); - check_config!(Staging, parsed, Config::default_for(Staging)); - check_config!(Production, parsed, Config::default_for(Production)); + check_config!(Staging, parsed, default_config(Staging)); + check_config!(Production, parsed, default_config(Production)); + expected.env = Staging; let stage_config = ["[stage]", config_str].join("\n"); - let parsed = RocketConfig::parse(stage_config, CONFIG_FILENAME); + let parsed = RocketConfig::parse(stage_config, TEST_CONFIG_FILENAME); check_config!(Staging, parsed, expected); - check_config!(Development, parsed, Config::default_for(Development)); - check_config!(Production, parsed, Config::default_for(Production)); + check_config!(Development, parsed, default_config(Development)); + check_config!(Production, parsed, default_config(Production)); + expected.env = Production; let prod_config = ["[prod]", config_str].join("\n"); - let parsed = RocketConfig::parse(prod_config, CONFIG_FILENAME); + let parsed = RocketConfig::parse(prod_config, TEST_CONFIG_FILENAME); check_config!(Production, parsed, expected); - check_config!(Development, parsed, Config::default_for(Development)); - check_config!(Staging, parsed, Config::default_for(Staging)); + check_config!(Development, parsed, default_config(Development)); + check_config!(Staging, parsed, default_config(Staging)); } #[test] @@ -317,29 +346,25 @@ mod test { check_config!(RocketConfig::parse(r#" [development] address = "localhost" - "#.to_string(), CONFIG_FILENAME) => { - address: "localhost".to_string(), - ..Config::default_for(Development) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Development).address("localhost".into()) }); check_config!(RocketConfig::parse(r#" [dev] address = "127.0.0.1" - "#.to_string(), CONFIG_FILENAME) => { - address: "127.0.0.1".to_string(), - ..Config::default_for(Development) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Development).address("127.0.0.1".into()) }); check_config!(RocketConfig::parse(r#" [dev] address = "0.0.0.0" - "#.to_string(), CONFIG_FILENAME) => { - address: "0.0.0.0".to_string(), - ..Config::default_for(Development) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Development).address("0.0.0.0".into()) }); } - #[test] fn test_bad_address_values() { // Take the lock so changing the environment doesn't cause races. @@ -349,27 +374,27 @@ mod test { assert!(RocketConfig::parse(r#" [development] address = 0000 - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [development] address = true - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [development] address = "_idk_" - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [staging] address = "1.2.3.4:100" - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [production] address = "1.2.3.4.5.6" - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); } #[test] @@ -381,17 +406,15 @@ mod test { check_config!(RocketConfig::parse(r#" [stage] port = 100 - "#.to_string(), CONFIG_FILENAME) => { - port: 100, - ..Config::default_for(Staging) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Staging).port(100) }); check_config!(RocketConfig::parse(r#" [stage] port = 6000 - "#.to_string(), CONFIG_FILENAME) => { - port: 6000, - ..Config::default_for(Staging) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Staging).port(6000) }); } @@ -404,17 +427,17 @@ mod test { assert!(RocketConfig::parse(r#" [development] port = true - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [production] port = "hello" - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [staging] port = -1 - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); } #[test] @@ -426,26 +449,23 @@ mod test { check_config!(RocketConfig::parse(r#" [stage] log = "normal" - "#.to_string(), CONFIG_FILENAME) => { - log_level: LoggingLevel::Normal, - ..Config::default_for(Staging) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Staging).log_level(LoggingLevel::Normal) }); check_config!(RocketConfig::parse(r#" [stage] log = "debug" - "#.to_string(), CONFIG_FILENAME) => { - log_level: LoggingLevel::Debug, - ..Config::default_for(Staging) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Staging).log_level(LoggingLevel::Debug) }); check_config!(RocketConfig::parse(r#" [stage] log = "critical" - "#.to_string(), CONFIG_FILENAME) => { - log_level: LoggingLevel::Critical, - ..Config::default_for(Staging) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Staging).log_level(LoggingLevel::Critical) }); } @@ -458,17 +478,17 @@ mod test { assert!(RocketConfig::parse(r#" [dev] log = false - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [development] log = 0 - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [prod] log = "no" - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); } #[test] @@ -480,17 +500,19 @@ mod test { check_config!(RocketConfig::parse(r#" [stage] session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5" - "#.to_string(), CONFIG_FILENAME) => { - session_key: Some("VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5".into()), - ..Config::default_for(Staging) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Staging).session_key( + "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5".into() + ) }); check_config!(RocketConfig::parse(r#" [stage] session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz" - "#.to_string(), CONFIG_FILENAME) => { - session_key: Some("adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz".into()), - ..Config::default_for(Staging) + "#.to_string(), TEST_CONFIG_FILENAME), { + default_config(Staging).session_key( + "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz".into() + ) }); } @@ -503,17 +525,17 @@ mod test { assert!(RocketConfig::parse(r#" [dev] session_key = true - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [dev] session_key = 1283724897238945234897 - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [dev] session_key = "abcv" - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); } #[test] @@ -524,16 +546,16 @@ mod test { assert!(RocketConfig::parse(r#" [dev - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [dev] 1.2.3 = 2 - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); assert!(RocketConfig::parse(r#" [dev] session_key = "abcv" = other - "#.to_string(), CONFIG_FILENAME).is_err()); + "#.to_string(), TEST_CONFIG_FILENAME).is_err()); } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index a8fe0dc0..c9813ac6 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,6 +1,7 @@ #![feature(question_mark)] #![feature(specialization)] #![feature(conservative_impl_trait)] +#![feature(drop_types_in_const)] //! # Rocket - Core API Documentation //! @@ -73,13 +74,13 @@ pub mod http; pub mod request; pub mod response; pub mod outcome; +pub mod config; mod error; mod router; mod rocket; mod codegen; mod catcher; -mod config; /// Defines the types for request and error handlers. #[doc(hidden)] diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index 9667a17b..6ed2f422 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -154,7 +154,7 @@ impl Rocket { if !responder.respond(response).is_success() { error_!("Catcher outcome was unsuccessul; aborting response."); } else { - info_!("Responded with catcher."); + info_!("Responded with {} catcher.", White.paint(code)); } } else { error_!("Catcher returned an incomplete response."); @@ -226,21 +226,23 @@ impl Rocket { } pub fn ignite() -> Rocket { - // Note: read_or_default will exit the process under errors. - let config = config::read_or_default(); + // Note: init() will exit the process under config errors. + let config = config::init(); - logger::init(config.active().log_level); - info!("🔧 Configured for {}.", config.active_env); + logger::init(config.log_level); + info!("🔧 Configured for {}.", config.env); info_!("listening: {}:{}", - White.paint(&config.active().address), - White.paint(&config.active().port)); - info_!("logging: {:?}", White.paint(config.active().log_level)); - info_!("session key: {}", - White.paint(config.active().session_key.is_some())); + White.paint(&config.address), + White.paint(&config.port)); + info_!("logging: {:?}", White.paint(config.log_level)); + info_!("session key: {}", White.paint(config.session_key.is_some())); + for (name, value) in config.extras() { + info_!("{} {}: {}", Yellow.paint("[extra]"), name, White.paint(value)); + } Rocket { - address: config.active().address.clone(), - port: config.active().port, + address: config.address.clone(), + port: config.port, router: Router::new(), default_catchers: catcher::defaults::get(), catchers: catcher::defaults::get(),