Add `config::get()`, for global config access. Use it for `Template`.

This commit is contained in:
Sergio Benitez 2016-10-14 18:57:36 -07:00
parent 722f613686
commit 1323e7a420
9 changed files with 320 additions and 176 deletions

View File

@ -16,7 +16,7 @@ use self::glob::glob;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::collections::HashMap; use std::collections::HashMap;
use rocket::Rocket; use rocket::config;
use rocket::response::{Content, ResponseOutcome, Responder}; use rocket::response::{Content, ResponseOutcome, Responder};
use rocket::http::hyper::FreshHyperResponse; use rocket::http::hyper::FreshHyperResponse;
use rocket::http::{ContentType, StatusCode}; use rocket::http::{ContentType, StatusCode};
@ -114,11 +114,22 @@ pub struct TemplateInfo {
data_type: Option<String> data_type: Option<String>
} }
const DEFAULT_TEMPLATE_DIR: &'static str = "templates";
lazy_static! { lazy_static! {
static ref TEMPLATES: HashMap<String, TemplateInfo> = discover_templates(); static ref TEMPLATES: HashMap<String, TemplateInfo> = discover_templates();
// FIXME: Ensure template_dir is an absolute path starting at crate root. static ref TEMPLATE_DIR: String = {
static ref TEMPLATE_DIR: String = config::active().map(|config| {
Rocket::config("template_dir").unwrap_or("templates").to_string(); 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 { impl Template {
@ -133,6 +144,7 @@ impl Template {
let template = TEMPLATES.get(name); let template = TEMPLATES.get(name);
if template.is_none() { if template.is_none() {
error_!("Template '{}' does not exist.", name); error_!("Template '{}' does not exist.", name);
info_!("Searched in '{}'.", *TEMPLATE_DIR);
return Template(None, None); return Template(None, None);
} }

View File

@ -5,6 +5,8 @@
address = "localhost" address = "localhost"
port = 8000 port = 8000
log = "normal" log = "normal"
hi = "Hello!"
is_extra = true
[staging] [staging]
address = "0.0.0.0" address = "0.0.0.0"

12
examples/todo/Rocket.toml Normal file
View File

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

View File

@ -1,8 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::path::Path;
use super::Environment::*; use config::Environment::*;
use super::Environment; use config::{self, Environment, ConfigError};
use logger::LoggingLevel; use logger::LoggingLevel;
use toml::Value; use toml::Value;
@ -13,28 +14,28 @@ pub struct Config {
pub port: usize, pub port: usize,
pub log_level: LoggingLevel, pub log_level: LoggingLevel,
pub session_key: Option<String>, pub session_key: Option<String>,
pub extra: HashMap<String, Value>, pub env: Environment,
extra: HashMap<String, Value>,
filename: String,
} }
macro_rules! parse { macro_rules! parse {
($val:expr, as_str) => ( ($conf:expr, $name:expr, $val:expr, $method:ident, $expect: expr) => (
match $val.as_str() { $val.$method().ok_or_else(|| {
Some(v) => v, $conf.bad_type($name, $val, $expect)
None => return Err("a string") })
}
);
($val:expr, as_integer) => (
match $val.as_integer() {
Some(v) => v,
None => return Err("an integer")
}
); );
} }
impl Config { impl Config {
pub fn default_for(env: Environment) -> Config { pub fn default_for(env: Environment, filename: &str) -> config::Result<Config> {
match env { 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 => { Development => {
Config { Config {
address: "localhost".to_string(), address: "localhost".to_string(),
@ -42,6 +43,8 @@ impl Config {
log_level: LoggingLevel::Normal, log_level: LoggingLevel::Normal,
session_key: None, session_key: None,
extra: HashMap::new(), extra: HashMap::new(),
env: env,
filename: filename.to_string(),
} }
} }
Staging => { Staging => {
@ -51,6 +54,8 @@ impl Config {
log_level: LoggingLevel::Normal, log_level: LoggingLevel::Normal,
session_key: None, session_key: None,
extra: HashMap::new(), extra: HashMap::new(),
env: env,
filename: filename.to_string(),
} }
} }
Production => { Production => {
@ -60,44 +65,118 @@ impl Config {
log_level: LoggingLevel::Critical, log_level: LoggingLevel::Critical,
session_key: None, session_key: None,
extra: HashMap::new(), 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" { 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(":") { 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() { } 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" { } else if name == "port" {
let port = parse!(value, as_integer); let port = parse!(self, name, val, as_integer, "an integer")?;
if port < 0 { if port < 0 {
return Err("an unsigned integer"); return Err(self.bad_type(name, val, "an unsigned integer"));
} }
self.port = port as usize; self.port = port as usize;
} else if name == "session_key" { } 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 { 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()); self.session_key = Some(key.to_string());
} else if name == "log" { } 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, 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 { } else {
self.extra.insert(name.into(), value.clone()); self.extra.insert(name.into(), val.clone());
} }
Ok(()) 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<i64> {
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<bool> {
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<f64> {
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<Item=(&'a String, &'a Value)> {
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
}
} }

View File

@ -16,6 +16,8 @@ pub enum ConfigError {
BadCWD, BadCWD,
NotFound, NotFound,
IOError, IOError,
/// (path, reason)
BadFilePath(String, &'static str),
/// (environment_name) /// (environment_name)
BadEnv(String), BadEnv(String),
/// (environment_name, filename) /// (environment_name, filename)
@ -35,6 +37,10 @@ impl ConfigError {
BadCWD => error!("couldn't get current working directory"), BadCWD => error!("couldn't get current working directory"),
NotFound => error!("config file was not found"), NotFound => error!("config file was not found"),
IOError => error!("failed reading the config file: IO error"), 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) => { BadEntry(ref name, ref filename) => {
error!("[{}] is not a known configuration environment", name); error!("[{}] is not a known configuration environment", name);
info_!("in {}", White.paint(filename)); info_!("in {}", White.paint(filename));
@ -63,4 +69,12 @@ impl ConfigError {
} }
} }
} }
pub fn is_not_found(&self) -> bool {
use self::ConfigError::*;
match *self {
NotFound => true,
_ => false
}
}
} }

View File

@ -17,8 +17,12 @@ use self::config::Config;
use toml::{self, Table}; use toml::{self, Table};
use logger::{self, LoggingLevel}; use logger::{self, LoggingLevel};
static mut CONFIG: Option<RocketConfig> = None;
const CONFIG_FILENAME: &'static str = "Rocket.toml"; const CONFIG_FILENAME: &'static str = "Rocket.toml";
pub type Result<T> = ::std::result::Result<T, ConfigError>;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct RocketConfig { pub struct RocketConfig {
pub active_env: Environment, pub active_env: Environment,
@ -28,7 +32,7 @@ pub struct RocketConfig {
impl RocketConfig { impl RocketConfig {
/// Iteratively search for `file` in `pwd` and its parents, returning the path /// 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. /// to the file or an Error::NoKey if the file couldn't be found.
fn find() -> Result<PathBuf, ConfigError> { fn find() -> Result<PathBuf> {
let cwd = env::current_dir().map_err(|_| ConfigError::BadCWD)?; let cwd = env::current_dir().map_err(|_| ConfigError::BadCWD)?;
let mut current = cwd.as_path(); let mut current = cwd.as_path();
@ -47,26 +51,24 @@ impl RocketConfig {
Err(ConfigError::NotFound) Err(ConfigError::NotFound)
} }
fn set(&mut self, env: Environment, kvs: &Table, filename: &str) fn set(&mut self, env: Environment, kvs: &Table)
-> Result<(), ConfigError> { -> Result<()> {
let config = self.config.entry(env).or_insert(Config::default_for(env)); let config = match self.config.get_mut(&env) {
Some(config) => config,
None => panic!("set(): {} config is missing.", env),
};
for (key, value) in kvs { for (key, value) in kvs {
if let Err(expected) = config.set(key, value) { config.set(key, value)?;
let name = format!("{}.{}", env, key);
return Err(ConfigError::BadType(
name, expected, value.type_str(), filename.to_string()
))
}
} }
Ok(()) Ok(())
} }
pub fn get(&self, env: Environment) -> &Config { pub fn get(&self, env: Environment) -> &Config {
if let Some(config) = self.config.get(&env) { match self.config.get(&env) {
config Some(config) => config,
} else { None => panic!("get(): {} config is missing.", env),
panic!("No value from environment: {:?}", env);
} }
} }
@ -74,7 +76,7 @@ impl RocketConfig {
self.get(self.active_env) self.get(self.active_env)
} }
fn parse(src: String, filename: &str) -> Result<RocketConfig, ConfigError> { fn parse(src: String, filename: &str) -> Result<RocketConfig> {
// Parse the source as TOML, if possible. // Parse the source as TOML, if possible.
let mut parser = toml::Parser::new(&src); let mut parser = toml::Parser::new(&src);
let toml = parser.parse().ok_or(ConfigError::ParseError( 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 // 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. // Parse the values from the TOML file.
for (entry, value) in toml { for (entry, value) in toml {
@ -106,13 +108,13 @@ impl RocketConfig {
}; };
// Set the environment configuration from the kv pairs. // Set the environment configuration from the kv pairs.
config.set(env, &kv_pairs, filename)?; config.set(env, &kv_pairs)?;
} }
Ok(config) Ok(config)
} }
pub fn read() -> Result<RocketConfig, ConfigError> { pub fn read() -> Result<RocketConfig> {
// Find the config file, starting from the `cwd` and working backwords. // Find the config file, starting from the `cwd` and working backwords.
let file = RocketConfig::find()?; let file = RocketConfig::find()?;
@ -127,34 +129,39 @@ impl RocketConfig {
RocketConfig::parse(contents, &file.to_string_lossy()) RocketConfig::parse(contents, &file.to_string_lossy())
} }
pub fn active_default() -> Result<RocketConfig, ConfigError> { pub fn active_default(filename: &str) -> Result<RocketConfig> {
let mut default = RocketConfig::default(); let mut defaults = HashMap::new();
default.active_env = Environment::active()?; defaults.insert(Development, Config::default_for(Development, filename)?);
Ok(default) 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 { /// Initializes the global RocketConfig by reading the Rocket config file from
fn default() -> RocketConfig { /// the current directory or any of its parents. Returns the active
RocketConfig { /// configuration, which is determined by the config env variable. If there as a
active_env: Environment::Development, /// problem parsing the configuration, the error is printed and the progam is
config: { /// aborted. If there an I/O issue reading the config file, a warning is printed
let mut default_config = HashMap::new(); /// and the default configuration is used. If there is no config file, the
default_config.insert(Development, Config::default_for(Development)); /// default configuration is used.
default_config.insert(Staging, Config::default_for(Staging)); ///
default_config.insert(Production, Config::default_for(Production)); /// # Panics
default_config ///
}, /// 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| -> ! { let bail = |e: ConfigError| -> ! {
logger::init(LoggingLevel::Debug); logger::init(LoggingLevel::Debug);
e.pretty_print(); e.pretty_print();
@ -162,40 +169,54 @@ pub fn read_or_default() -> RocketConfig {
}; };
use self::ConfigError::*; use self::ConfigError::*;
RocketConfig::read().unwrap_or_else(|e| { let config = RocketConfig::read().unwrap_or_else(|e| {
match e { match e {
ParseError(..) | BadEntry(..) | BadEnv(..) | BadType(..) => bail(e), ParseError(..) | BadEntry(..) | BadEnv(..) | BadType(..)
IOError | BadCWD => warn!("failed reading Rocket.toml. using defaults"), | BadFilePath(..) => bail(e),
IOError | BadCWD => warn!("Failed reading Rocket.toml. Using defaults."),
NotFound => { /* try using the default below */ } 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)] #[cfg(test)]
mod test { mod test {
use std::env; use std::env;
use std::collections::HashMap;
use std::sync::Mutex; use std::sync::Mutex;
use super::{RocketConfig, CONFIG_FILENAME, ConfigError}; use super::{RocketConfig, ConfigError};
use super::environment::CONFIG_ENV; use super::environment::{Environment, CONFIG_ENV};
use super::Environment::*; use super::Environment::*;
use super::config::Config; use super::config::Config;
use super::Result;
use ::toml::Value; use ::toml::Value;
use ::logger::LoggingLevel; use ::logger::LoggingLevel;
const TEST_CONFIG_FILENAME: &'static str = "/tmp/testing/Rocket.toml";
lazy_static! { lazy_static! {
static ref ENV_LOCK: Mutex<usize> = Mutex::new(0); static ref ENV_LOCK: Mutex<usize> = Mutex::new(0);
} }
macro_rules! check_config { macro_rules! check_config {
($rconfig:expr => { $($param:tt)+ }) => (
check_config!($rconfig, Config { $($param)+ })
);
($rconfig:expr, $econfig:expr) => ( ($rconfig:expr, $econfig:expr) => (
match $rconfig { match $rconfig {
Ok(config) => assert_eq!(config.active(), &$econfig), Ok(config) => assert_eq!(config.active(), &$econfig),
@ -211,6 +232,14 @@ mod test {
); );
} }
fn active_default() -> Result<RocketConfig> {
RocketConfig::active_default(TEST_CONFIG_FILENAME)
}
fn default_config(env: Environment) -> Config {
Config::default_for(env, TEST_CONFIG_FILENAME).expect("config")
}
#[test] #[test]
fn test_defaults() { fn test_defaults() {
// Take the lock so changing the environment doesn't cause races. // 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. // First, without an environment. Should get development defaults.
env::remove_var(CONFIG_ENV); 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. // Now with an explicit dev environment.
for env in &["development", "dev"] { for env in &["development", "dev"] {
env::set_var(CONFIG_ENV, env); env::set_var(CONFIG_ENV, env);
check_config!(RocketConfig::active_default(), Config::default_for(Development)); check_config!(active_default(), default_config(Development));
} }
// Now staging. // Now staging.
for env in &["stage", "staging"] { for env in &["stage", "staging"] {
env::set_var(CONFIG_ENV, env); env::set_var(CONFIG_ENV, env);
check_config!(RocketConfig::active_default(), Config::default_for(Staging)); check_config!(active_default(), default_config(Staging));
} }
// Finally, production. // Finally, production.
for env in &["prod", "production"] { for env in &["prod", "production"] {
env::set_var(CONFIG_ENV, env); 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!", "🚀 "] { for env in &["", "p", "pr", "pro", "prodo", " prod", "dev ", "!dev!", "🚀 "] {
env::set_var(CONFIG_ENV, env); env::set_var(CONFIG_ENV, env);
let err = ConfigError::BadEnv(env.to_string()); 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. // Test that a bunch of invalid environment names give the right error.
env::remove_var(CONFIG_ENV); env::remove_var(CONFIG_ENV);
for env in &["p", "pr", "pro", "prodo", "bad", "meow", "this", "that"] { for env in &["p", "pr", "pro", "prodo", "bad", "meow", "this", "that"] {
let toml_table = format!("[{}]\n", env); let toml_table = format!("[{}]\n", env);
let err = ConfigError::BadEntry(env.to_string(), CONFIG_FILENAME.into()); let e_str = env.to_string();
assert!(RocketConfig::parse(toml_table, CONFIG_FILENAME) 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)); .err().map_or(false, |e| e == err));
} }
} }
@ -276,36 +306,35 @@ mod test {
pi = 3.14 pi = 3.14
"#; "#;
let mut extra: HashMap<String, Value> = HashMap::new(); let mut expected = default_config(Development);
extra.insert("template_dir".to_string(), Value::String("mine".into())); expected.address = "1.2.3.4".to_string();
extra.insert("json".to_string(), Value::Boolean(true)); expected.port = 7810;
extra.insert("pi".to_string(), Value::Float(3.14)); expected.log_level = LoggingLevel::Critical;
expected.session_key = Some("01234567890123456789012345678901".to_string());
let expected = Config { expected.set("template_dir", &Value::String("mine".into())).unwrap();
address: "1.2.3.4".to_string(), expected.set("json", &Value::Boolean(true)).unwrap();
port: 7810, expected.set("pi", &Value::Float(3.14)).unwrap();
log_level: LoggingLevel::Critical,
session_key: Some("01234567890123456789012345678901".to_string()),
extra: extra
};
expected.env = Development;
let dev_config = ["[dev]", config_str].join("\n"); 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!(Development, parsed, expected);
check_config!(Staging, parsed, Config::default_for(Staging)); check_config!(Staging, parsed, default_config(Staging));
check_config!(Production, parsed, Config::default_for(Production)); check_config!(Production, parsed, default_config(Production));
expected.env = Staging;
let stage_config = ["[stage]", config_str].join("\n"); 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!(Staging, parsed, expected);
check_config!(Development, parsed, Config::default_for(Development)); check_config!(Development, parsed, default_config(Development));
check_config!(Production, parsed, Config::default_for(Production)); check_config!(Production, parsed, default_config(Production));
expected.env = Production;
let prod_config = ["[prod]", config_str].join("\n"); 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!(Production, parsed, expected);
check_config!(Development, parsed, Config::default_for(Development)); check_config!(Development, parsed, default_config(Development));
check_config!(Staging, parsed, Config::default_for(Staging)); check_config!(Staging, parsed, default_config(Staging));
} }
#[test] #[test]
@ -317,29 +346,25 @@ mod test {
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[development] [development]
address = "localhost" address = "localhost"
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
address: "localhost".to_string(), default_config(Development).address("localhost".into())
..Config::default_for(Development)
}); });
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[dev] [dev]
address = "127.0.0.1" address = "127.0.0.1"
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
address: "127.0.0.1".to_string(), default_config(Development).address("127.0.0.1".into())
..Config::default_for(Development)
}); });
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[dev] [dev]
address = "0.0.0.0" address = "0.0.0.0"
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
address: "0.0.0.0".to_string(), default_config(Development).address("0.0.0.0".into())
..Config::default_for(Development)
}); });
} }
#[test] #[test]
fn test_bad_address_values() { fn test_bad_address_values() {
// Take the lock so changing the environment doesn't cause races. // Take the lock so changing the environment doesn't cause races.
@ -349,27 +374,27 @@ mod test {
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[development] [development]
address = 0000 address = 0000
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[development] [development]
address = true address = true
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[development] [development]
address = "_idk_" address = "_idk_"
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[staging] [staging]
address = "1.2.3.4:100" address = "1.2.3.4:100"
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[production] [production]
address = "1.2.3.4.5.6" address = "1.2.3.4.5.6"
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
} }
#[test] #[test]
@ -381,17 +406,15 @@ mod test {
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[stage] [stage]
port = 100 port = 100
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
port: 100, default_config(Staging).port(100)
..Config::default_for(Staging)
}); });
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[stage] [stage]
port = 6000 port = 6000
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
port: 6000, default_config(Staging).port(6000)
..Config::default_for(Staging)
}); });
} }
@ -404,17 +427,17 @@ mod test {
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[development] [development]
port = true port = true
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[production] [production]
port = "hello" port = "hello"
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[staging] [staging]
port = -1 port = -1
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
} }
#[test] #[test]
@ -426,26 +449,23 @@ mod test {
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[stage] [stage]
log = "normal" log = "normal"
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
log_level: LoggingLevel::Normal, default_config(Staging).log_level(LoggingLevel::Normal)
..Config::default_for(Staging)
}); });
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[stage] [stage]
log = "debug" log = "debug"
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
log_level: LoggingLevel::Debug, default_config(Staging).log_level(LoggingLevel::Debug)
..Config::default_for(Staging)
}); });
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[stage] [stage]
log = "critical" log = "critical"
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
log_level: LoggingLevel::Critical, default_config(Staging).log_level(LoggingLevel::Critical)
..Config::default_for(Staging)
}); });
} }
@ -458,17 +478,17 @@ mod test {
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[dev] [dev]
log = false log = false
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[development] [development]
log = 0 log = 0
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[prod] [prod]
log = "no" log = "no"
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
} }
#[test] #[test]
@ -480,17 +500,19 @@ mod test {
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[stage] [stage]
session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5" session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
session_key: Some("VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5".into()), default_config(Staging).session_key(
..Config::default_for(Staging) "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5".into()
)
}); });
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[stage] [stage]
session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz" session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
"#.to_string(), CONFIG_FILENAME) => { "#.to_string(), TEST_CONFIG_FILENAME), {
session_key: Some("adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz".into()), default_config(Staging).session_key(
..Config::default_for(Staging) "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz".into()
)
}); });
} }
@ -503,17 +525,17 @@ mod test {
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[dev] [dev]
session_key = true session_key = true
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[dev] [dev]
session_key = 1283724897238945234897 session_key = 1283724897238945234897
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[dev] [dev]
session_key = "abcv" session_key = "abcv"
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
} }
#[test] #[test]
@ -524,16 +546,16 @@ mod test {
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[dev [dev
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[dev] [dev]
1.2.3 = 2 1.2.3 = 2
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#" assert!(RocketConfig::parse(r#"
[dev] [dev]
session_key = "abcv" = other session_key = "abcv" = other
"#.to_string(), CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
} }
} }

View File

@ -1,6 +1,7 @@
#![feature(question_mark)] #![feature(question_mark)]
#![feature(specialization)] #![feature(specialization)]
#![feature(conservative_impl_trait)] #![feature(conservative_impl_trait)]
#![feature(drop_types_in_const)]
//! # Rocket - Core API Documentation //! # Rocket - Core API Documentation
//! //!
@ -73,13 +74,13 @@ pub mod http;
pub mod request; pub mod request;
pub mod response; pub mod response;
pub mod outcome; pub mod outcome;
pub mod config;
mod error; mod error;
mod router; mod router;
mod rocket; mod rocket;
mod codegen; mod codegen;
mod catcher; mod catcher;
mod config;
/// Defines the types for request and error handlers. /// Defines the types for request and error handlers.
#[doc(hidden)] #[doc(hidden)]

View File

@ -154,7 +154,7 @@ impl Rocket {
if !responder.respond(response).is_success() { if !responder.respond(response).is_success() {
error_!("Catcher outcome was unsuccessul; aborting response."); error_!("Catcher outcome was unsuccessul; aborting response.");
} else { } else {
info_!("Responded with catcher."); info_!("Responded with {} catcher.", White.paint(code));
} }
} else { } else {
error_!("Catcher returned an incomplete response."); error_!("Catcher returned an incomplete response.");
@ -226,21 +226,23 @@ impl Rocket {
} }
pub fn ignite() -> Rocket { pub fn ignite() -> Rocket {
// Note: read_or_default will exit the process under errors. // Note: init() will exit the process under config errors.
let config = config::read_or_default(); let config = config::init();
logger::init(config.active().log_level); logger::init(config.log_level);
info!("🔧 Configured for {}.", config.active_env); info!("🔧 Configured for {}.", config.env);
info_!("listening: {}:{}", info_!("listening: {}:{}",
White.paint(&config.active().address), White.paint(&config.address),
White.paint(&config.active().port)); White.paint(&config.port));
info_!("logging: {:?}", White.paint(config.active().log_level)); info_!("logging: {:?}", White.paint(config.log_level));
info_!("session key: {}", info_!("session key: {}", White.paint(config.session_key.is_some()));
White.paint(config.active().session_key.is_some())); for (name, value) in config.extras() {
info_!("{} {}: {}", Yellow.paint("[extra]"), name, White.paint(value));
}
Rocket { Rocket {
address: config.active().address.clone(), address: config.address.clone(),
port: config.active().port, port: config.port,
router: Router::new(), router: Router::new(),
default_catchers: catcher::defaults::get(), default_catchers: catcher::defaults::get(),
catchers: catcher::defaults::get(), catchers: catcher::defaults::get(),