summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Leckey <ryan@launchbadge.com>2017-06-13 18:55:40 -0700
committerRyan Leckey <ryan@launchbadge.com>2017-06-13 18:55:40 -0700
commit47ad966e064f842056e7c1b7abe8ef64d127f5be (patch)
tree4dd4e14168383dd818fe6add8ed0a5f4900d85b6
parent1266d110ba31a9d0f9f4908a5a30291899ffc8fc (diff)
parent312f32905f518fa59e1daf3fc8224dadf584b484 (diff)
Merge branch 'serde'
-rw-r--r--.travis.yml13
-rw-r--r--Cargo.toml17
-rw-r--r--examples/basic/Cargo.toml6
-rw-r--r--examples/basic/src/main.rs33
-rw-r--r--examples/file-json/Cargo.toml6
-rw-r--r--examples/file-json/Settings.json5
-rw-r--r--examples/file-json/src/main.rs12
-rw-r--r--examples/file-toml/Cargo.toml6
-rw-r--r--examples/file-toml/Settings.toml3
-rw-r--r--examples/file-toml/src/main.rs12
-rw-r--r--examples/file-yaml/Cargo.toml6
-rw-r--r--examples/file-yaml/Settings.yaml3
-rw-r--r--examples/file-yaml/src/main.rs12
-rw-r--r--src/config.rs745
-rw-r--r--src/de.rs320
-rw-r--r--src/env.rs88
-rw-r--r--src/error.rs194
-rw-r--r--src/file/format/json.rs77
-rw-r--r--src/file/format/mod.rs61
-rw-r--r--src/file/format/toml.rs69
-rw-r--r--src/file/format/yaml.rs93
-rw-r--r--src/file/json.rs87
-rw-r--r--src/file/mod.rs236
-rw-r--r--src/file/nil.rs13
-rw-r--r--src/file/source/file.rs129
-rw-r--r--src/file/source/mod.rs12
-rw-r--r--src/file/source/string.rs21
-rw-r--r--src/file/toml.rs74
-rw-r--r--src/file/yaml.rs101
-rw-r--r--src/lib.rs94
-rw-r--r--src/path/mod.rs161
-rw-r--r--src/path/parser.rs (renamed from src/path.rs)45
-rw-r--r--src/source.rs14
-rw-r--r--src/value.rs571
-rw-r--r--tests/Settings-invalid.json4
-rw-r--r--tests/Settings-invalid.toml2
-rw-r--r--tests/Settings-invalid.yaml2
-rw-r--r--tests/Settings-production.toml8
-rw-r--r--tests/Settings.json16
-rw-r--r--tests/Settings.toml20
-rw-r--r--tests/Settings.yaml12
-rw-r--r--tests/errors.rs47
-rw-r--r--tests/file.rs22
-rw-r--r--tests/file_json.rs71
-rw-r--r--tests/file_toml.rs71
-rw-r--r--tests/file_yaml.rs71
-rw-r--r--tests/get.rs170
-rw-r--r--tests/merge.rs24
-rw-r--r--tests/set.rs40
49 files changed, 2488 insertions, 1431 deletions
diff --git a/.travis.yml b/.travis.yml
index c088006..8137b0a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,14 @@
+sudo: true
language: rust
+cache: cargo
rust:
- - nightly \ No newline at end of file
+ - nightly
+ - beta
+ - stable
+
+matrix:
+ allow_failures:
+ - rust: nightly
+
+script:
+ - cargo test
diff --git a/Cargo.toml b/Cargo.toml
index 0f9fad1..4c57803 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "config"
-version = "0.4.1"
+version = "0.5.0-pre"
description = "Layered configuration system for Rust applications."
homepage = "https://github.com/mehcode/config-rs"
repository = "https://github.com/mehcode/config-rs"
@@ -10,13 +10,18 @@ authors = ["Ryan Leckey <leckey.ryan@gmail.com>"]
license = "MIT/Apache-2.0"
[features]
-default = ["toml"]
+default = ["toml", "json", "yaml"]
json = ["serde_json"]
yaml = ["yaml-rust"]
[dependencies]
-nom = "^2.1"
+serde = "^1.0.8"
+nom = "^3.0.0"
-toml = { version = "0.2.1", optional = true }
-serde_json = { version = "0.9", optional = true }
-yaml-rust = { version = "0.3.5", optional = true }
+toml = { version = "^0.4.1", optional = true }
+serde_json = { version = "^1.0.2", optional = true }
+yaml-rust = { version = "^0.3.5", optional = true }
+
+[dev-dependencies]
+serde_derive = "^1.0.8"
+float-cmp = "*"
diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml
deleted file mode 100644
index 7ede162..0000000
--- a/examples/basic/Cargo.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[package]
-name = "basic"
-version = "0.1.0"
-
-[dependencies]
-config = { path = "../..", default-features = false }
diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs
deleted file mode 100644
index 2f947d8..0000000
--- a/examples/basic/src/main.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-extern crate config;
-
-fn main() {
- let mut c = config::Config::new();
-
- // Set defaults for `window.width` and `window.height`
- c.set_default("window.title", "Basic").unwrap();
- c.set_default("window.width", 640).unwrap();
- c.set_default("window.height", 480).unwrap();
- c.set_default("debug", true).unwrap();
-
- // Note that you can retrieve the stored values as any type as long
- // as there exists a reasonable conversion
- println!("window.title : {:?}", c.get_str("window.title"));
- println!("window.width : {:?}", c.get_str("window.width"));
- println!("window.width : {:?}", c.get_int("window.width"));
- println!("debug : {:?}", c.get_bool("debug"));
- println!("debug : {:?}", c.get_str("debug"));
- println!("debug : {:?}", c.get_int("debug"));
-
- // Attempting to get a value as a type that cannot be reasonably
- // converted to will return None
- println!("window.title : {:?}", c.get_bool("window.title"));
-
- // Instead of using a get_* function you can get the variant
- // directly
- println!("debug : {:?}", c.get("debug"));
- println!("debug : {:?}",
- c.get("debug").unwrap().into_int());
-
- // Attempting to get a value that does not exist will return None
- println!("not-found : {:?}", c.get("not-found"));
-}
diff --git a/examples/file-json/Cargo.toml b/examples/file-json/Cargo.toml
deleted file mode 100644
index 7223f35..0000000
--- a/examples/file-json/Cargo.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[package]
-name = "file-json"
-version = "0.1.0"
-
-[dependencies]
-config = { path = "../..", default-features = false, features = ["json"] }
diff --git a/examples/file-json/Settings.json b/examples/file-json/Settings.json
deleted file mode 100644
index 72b28e6..0000000
--- a/examples/file-json/Settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "debug": false,
- "pi": 3.14159,
- "weight": 150
-}
diff --git a/examples/file-json/src/main.rs b/examples/file-json/src/main.rs
deleted file mode 100644
index e4ff809..0000000
--- a/examples/file-json/src/main.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-extern crate config;
-
-fn main() {
- let mut c = config::Config::new();
-
- // Read configuration from "Settings.json"
- c.merge(config::File::new("Settings", config::FileFormat::Json)).unwrap();
-
- println!("debug = {:?}", c.get("debug"));
- println!("pi = {:?}", c.get("pi"));
- println!("weight = {:?}", c.get("weight"));
-}
diff --git a/examples/file-toml/Cargo.toml b/examples/file-toml/Cargo.toml
deleted file mode 100644
index 6f45799..0000000
--- a/examples/file-toml/Cargo.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[package]
-name = "file-toml"
-version = "0.1.0"
-
-[dependencies]
-config = { path = "../.." }
diff --git a/examples/file-toml/Settings.toml b/examples/file-toml/Settings.toml
deleted file mode 100644
index 28c5fc6..0000000
--- a/examples/file-toml/Settings.toml
+++ /dev/null
@@ -1,3 +0,0 @@
-debug = false
-pi = 3.14159
-weight = 150
diff --git a/examples/file-toml/src/main.rs b/examples/file-toml/src/main.rs
deleted file mode 100644
index 85db701..0000000
--- a/examples/file-toml/src/main.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-extern crate config;
-
-fn main() {
- let mut c = config::Config::new();
-
- // Read configuration from "Settings.toml"
- c.merge(config::File::new("Settings", config::FileFormat::Toml)).unwrap();
-
- println!("debug = {:?}", c.get("debug"));
- println!("pi = {:?}", c.get("pi"));
- println!("weight = {:?}", c.get("weight"));
-}
diff --git a/examples/file-yaml/Cargo.toml b/examples/file-yaml/Cargo.toml
deleted file mode 100644
index 70176b1..0000000
--- a/examples/file-yaml/Cargo.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-[package]
-name = "file-yaml"
-version = "0.1.0"
-
-[dependencies]
-config = { path = "../..", default-features = false, features = ["yaml"] }
diff --git a/examples/file-yaml/Settings.yaml b/examples/file-yaml/Settings.yaml
deleted file mode 100644
index d92f6ad..0000000
--- a/examples/file-yaml/Settings.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-debug: false
-pi: 3.14159
-weight: 150
diff --git a/examples/file-yaml/src/main.rs b/examples/file-yaml/src/main.rs
deleted file mode 100644
index 6d72976..0000000
--- a/examples/file-yaml/src/main.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-extern crate config;
-
-fn main() {
- let mut c = config::Config::new();
-
- // Read configuration from "Settings.yaml"
- c.merge(config::File::new("Settings", config::FileFormat::Yaml)).unwrap();
-
- println!("debug = {:?}", c.get("debug"));
- println!("pi = {:?}", c.get("pi"));
- println!("weight = {:?}", c.get("weight"));
-}
diff --git a/src/config.rs b/src/config.rs
index ffe341a..0d84906 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,45 +1,28 @@
-use value::Value;
-use source::{Source, SourceBuilder};
-use path;
-
-use std::error::Error;
-use std::fmt;
-use std::str::FromStr;
use std::collections::HashMap;
+use serde::de::Deserialize;
-#[derive(Default, Debug)]
-pub struct FrozenError {}
+use error::*;
+use source::Source;
-impl fmt::Display for FrozenError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "FrozenError")
- }
-}
-
-impl Error for FrozenError {
- fn description(&self) -> &'static str {
- "configuration is frozen"
- }
-}
+use value::{Value, ValueWithKey};
+use path;
-// Underlying storage for the configuration
-enum ConfigStore {
+enum ConfigKind {
+ // A mutable configuration. This is the default.
Mutable {
- defaults: HashMap<String, Value>,
- overrides: HashMap<String, Value>,
-
- // Ordered list of sources
+ defaults: HashMap<path::Expression, Value>,
+ overrides: HashMap<path::Expression, Value>,
sources: Vec<Box<Source + Send + Sync>>,
},
- // TODO: Will be used for frozen configuratino soon
- #[allow(dead_code)]
+ // A frozen configuration.
+ // Configuration can no longer be mutated.
Frozen,
}
-impl Default for ConfigStore {
+impl Default for ConfigKind {
fn default() -> Self {
- ConfigStore::Mutable {
+ ConfigKind::Mutable {
defaults: HashMap::new(),
overrides: HashMap::new(),
sources: Vec::new(),
@@ -47,665 +30,165 @@ impl Default for ConfigStore {
}
}
-fn merge_in_all(r: &mut HashMap<String, Value>, map: &HashMap<String, Value>) {
- for (key, value) in map {
- path_set_str(r, key, value);
- }
-}
-
-// Child ( Child ( Identifier( "x" ), "y" ), "z" )
-fn path_get_mut<'a>(root: &'a mut HashMap<String, Value>,
- expr: path::Expression)
- -> Option<&'a mut Value> {
- match expr {
- path::Expression::Identifier(text) => Some(root.entry(text.clone()).or_insert(Value::Nil)),
-
- path::Expression::Child(expr, member) => {
- match path_get_mut(root, *expr) {
- Some(&mut Value::Table(ref mut table)) => {
- Some(table.entry(member.clone()).or_insert(Value::Nil))
- }
-
- Some(v @ _) => {
- *v = Value::Table(HashMap::new());
- if let Value::Table(ref mut table) = *v {
- Some(table.entry(member.clone()).or_insert(Value::Nil))
- } else {
- None
- }
- }
-
- _ => None,
- }
- }
-
- path::Expression::Subscript(expr, mut index) => {
- match path_get_mut(root, *expr) {
- Some(&mut Value::Array(ref mut array)) => {
- let len = array.len() as i32;
-
- if index < 0 {
- index = len + index;
- }
-
- if index < 0 {
- None
- } else {
- // Ensure there is enough room
- array.resize((index + 1) as usize, Value::Nil);
+/// A prioritized configuration repository. It maintains a set of
+/// configuration sources, fetches values to populate those, and provides
+/// them according to the source's priority.
+#[derive(Default)]
+pub struct Config {
+ kind: ConfigKind,
- Some(&mut array[index as usize])
- }
- }
+ /// Root of the cached configuration.
+ pub cache: Value,
+}
- _ => None,
- }
- }
+impl Config {
+ pub fn new() -> Self {
+ Config::default()
}
-}
-fn require_table(r: &mut HashMap<String, Value>, key: &String) {
- if r.contains_key(key) {
- // Coerce to table
- match *r.get(key).unwrap() {
- Value::Table(_) => {
- // Do nothing; already table
+ /// Merge in a configuration property source.
+ pub fn merge<T>(&mut self, source: T) -> Result<()>
+ where T: 'static,
+ T: Source + Send + Sync
+ {
+ match self.kind {
+ ConfigKind::Mutable { ref mut sources, .. } => {
+ sources.push(Box::new(source));
}
- _ => {
- // Override with empty table
- r.insert(key.clone(), Value::Table(HashMap::new()));
+ ConfigKind::Frozen => {
+ return Err(ConfigError::Frozen);
}
}
- } else {
- // Insert table
- r.insert(key.clone(), Value::Table(HashMap::new()));
- }
-}
-fn path_set(root: &mut HashMap<String, Value>, expr: path::Expression, value: &Value) {
- match expr {
- path::Expression::Identifier(text) => {
- match *value {
- Value::Table(ref table_v) => {
- require_table(root, &text);
- if let Value::Table(ref mut target) = *root.get_mut(&text).unwrap() {
- merge_in_all(target, table_v);
- }
- }
+ self.refresh()
+ }
- _ => {
- root.insert(text, value.clone());
+ /// Refresh the configuration cache with fresh
+ /// data from added sources.
+ ///
+ /// Configuration is automatically refreshed after a mutation
+ /// operation (`set`, `merge`, `set_default`, etc.).
+ pub fn refresh(&mut self) -> Result<()> {
+ self.cache = match self.kind {
+ // TODO: We need to actually merge in all the stuff
+ ConfigKind::Mutable {
+ ref overrides,
+ ref sources,
+ ref defaults,
+ } => {
+ let mut cache: Value = HashMap::<String, Value>::new().into();
+
+ // Add defaults
+ for (key, val) in defaults {
+ key.set(&mut cache, val.clone());
}
- }
- }
- path::Expression::Child(expr, member) => {
- if let Some(parent) = path_get_mut(root, *expr) {
- match *parent {
- Value::Table(ref mut table) => {
- path_set(table, path::Expression::Identifier(member), value);
- }
-
- _ => {
- // Coerce to a table and do the insert anyway
- *parent = Value::Table(HashMap::new());
- if let Value::Table(ref mut table) = *parent {
- path_set(table, path::Expression::Identifier(member), value);
- }
+ // Add sources
+ for source in sources {
+ let props = source.collect()?;
+ for (key, val) in &props {
+ path::Expression::Identifier(key.clone()).set(&mut cache, val.clone());
}
}
- }
- }
- path::Expression::Subscript(inner_expr, mut index) => {
- if let Some(parent) = path_get_mut(root, *inner_expr) {
- match *parent {
- Value::Array(ref mut array) => {
- let len = array.len() as i32;
-
- if index < 0 {
- index = len + index;
- }
-
- if index >= 0 {
- array[index as usize] = value.clone();
- }
- }
-
- Value::Nil => {
- // Add an array and do this again
- *parent = Value::Array(Vec::new());
- if let Value::Array(ref mut array) = *parent {
- let len = array.len() as i32;
-
- if index < 0 {
- index = len + index;
- }
-
- if index >= 0 {
- array.resize((index + 1) as usize, Value::Nil);
- array[index as usize] = value.clone();
- }
- }
- }
-
- _ => {
- // Do nothing
- }
+ // Add overrides
+ for (key, val) in overrides {
+ key.set(&mut cache, val.clone());
}
- }
- }
- }
-}
-fn path_set_str(root: &mut HashMap<String, Value>, key: &str, value: &Value) {
- match path::Expression::from_str(key) {
- Ok(expr) => {
- path_set(root, expr, value);
- }
-
- Err(_) => {
- // TODO: Log warning here
- }
- };
-}
+ cache
+ },
-impl ConfigStore {
- fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>>
- where T: SourceBuilder + Send + Sync
- {
- if let ConfigStore::Mutable { ref mut sources, .. } = *self {
- sources.push(source.build()?);
+ ConfigKind::Frozen => {
+ return Err(ConfigError::Frozen);
+ }
+ };
- Ok(())
- } else {
- Err(FrozenError::default().into())
- }
+ Ok(())
}
- fn set_default<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value>
- {
- if let ConfigStore::Mutable { ref mut defaults, .. } = *self {
- path_set_str(defaults, &key.to_lowercase(), &value.into());
-
- Ok(())
- } else {
- Err(FrozenError::default().into())
- }
+ /// Deserialize the entire configuration.
+ pub fn deserialize<'de, T: Deserialize<'de>>(&self) -> Result<T> {
+ T::deserialize(self.cache.clone())
}
- fn set<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
+ pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<()>
where T: Into<Value>
{
- if let ConfigStore::Mutable { ref mut overrides, .. } = *self {
- path_set_str(overrides, &key.to_lowercase(), &value.into());
-
- Ok(())
- } else {
- Err(FrozenError::default().into())
- }
- }
-
- fn collect(&self) -> Result<HashMap<String, Value>, Box<Error>> {
- if let ConfigStore::Mutable { ref overrides, ref sources, ref defaults } = *self {
- let mut r = HashMap::<String, Value>::new();
-
- merge_in_all(&mut r, defaults);
-
- for source in sources {
- merge_in_all(&mut r, &source.collect());
+ match self.kind {
+ ConfigKind::Mutable {
+ ref mut defaults,
+ ..
+ } => {
+ defaults.insert(key.parse()?, value.into());
}
- merge_in_all(&mut r, overrides);
-
- Ok(r)
- } else {
- Err(FrozenError::default().into())
- }
- }
-}
-
-#[derive(Default)]
-pub struct Config {
- store: ConfigStore,
-
- /// Top-level table of the cached configuration
- ///
- /// As configuration sources are merged with `Config::merge`, this
- /// cache is updated.
- cache: HashMap<String, Value>,
-}
-
-impl Config {
- pub fn new() -> Self {
- Default::default()
- }
-
- /// Merge in configuration values from the given source.
- pub fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>>
- where T: SourceBuilder + Send + Sync
- {
- self.store.merge(source)?;
- self.refresh()?;
-
- Ok(())
- }
-
- /// Sets the default value for this key. The default value is only used
- /// when no other value is provided.
- pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value>
- {
- self.store.set_default(key, value)?;
- self.refresh()?;
+ ConfigKind::Frozen => {
+ return Err(ConfigError::Frozen)
+ }
+ };
- Ok(())
+ self.refresh()
}
- /// Sets an override for this key.
- pub fn set<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
+ pub fn set<T>(&mut self, key: &str, value: T) -> Result<()>
where T: Into<Value>
{
- self.store.set(key, value)?;
- self.refresh()?;
-
- Ok(())
- }
+ match self.kind {
+ ConfigKind::Mutable {
+ ref mut overrides,
+ ..
+ } => {
+ overrides.insert(key.parse()?, value.into());
+ }
- /// Refresh the configuration cache with fresh
- /// data from associated sources.
- ///
- /// Configuration is automatically refreshed after a mutation
- /// operation (`set`, `merge`, `set_default`, etc.).
- pub fn refresh(&mut self) -> Result<(), Box<Error>> {
- self.cache = self.store.collect()?;
+ ConfigKind::Frozen => {
+ return Err(ConfigError::Frozen)
+ }
+ };
- Ok(())
+ self.refresh()
}
- // Child ( Child ( Identifier( "x" ), "y" ), "z" )
- fn path_get<'a>(&'a self, expr: path::Expression) -> Option<&'a Value> {
- match expr {
- path::Expression::Identifier(text) => self.cache.get(&text),
+ pub fn get<'de, T: Deserialize<'de>>(&self, key: &'de str) -> Result<T> {
+ // Parse the key into a path expression
+ let expr: path::Expression = key.to_lowercase().parse()?;
- path::Expression::Child(expr, member) => {
- match self.path_get(*expr) {
- Some(&Value::Table(ref table)) => table.get(&member),
+ // Traverse the cache using the path to (possibly) retrieve a value
+ let value = expr.get(&self.cache).cloned();
- _ => None,
- }
+ match value {
+ Some(value) => {
+ // Deserialize the received value into the requested type
+ T::deserialize(ValueWithKey::new(value, key))
}
- path::Expression::Subscript(expr, mut index) => {
- match self.path_get(*expr) {
- Some(&Value::Array(ref array)) => {
- let len = array.len() as i32;
-
- if index < 0 {
- index = len + index;
- }
-
- if index < 0 || index >= len {
- None
- } else {
- Some(&array[index as usize])
- }
- }
-
- _ => None,
- }
- }
+ None => Err(ConfigError::NotFound(key.into())),
}
}
- pub fn get(&self, key