summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Leckey <leckey.ryan@gmail.com>2017-03-08 11:09:37 -0800
committerRyan Leckey <leckey.ryan@gmail.com>2017-03-08 11:09:37 -0800
commit2dc6a74b84825f65142c1fa7d3e67cd4f35ee3cb (patch)
tree23b21f732efbb215498db6debf6dbaee3af7e94f
parentc9ee1568fe212e4c352ec1afc52db44b34348fcd (diff)
Initial work on deep serde integration
-rw-r--r--Cargo.toml30
-rw-r--r--examples/basic/Cargo.toml3
-rw-r--r--examples/basic/src/main.rs50
-rw-r--r--examples/file-json/Cargo.toml3
-rw-r--r--examples/file-toml/Cargo.toml5
-rw-r--r--examples/file-toml/Settings.toml3
-rw-r--r--examples/file-toml/src/main.rs18
-rw-r--r--examples/file-yaml/Cargo.toml3
-rw-r--r--lib/Cargo.toml24
-rw-r--r--lib/src/config.rs103
-rw-r--r--lib/src/de.rs257
-rw-r--r--lib/src/error.rs154
-rw-r--r--lib/src/file/format/mod.rs60
-rw-r--r--lib/src/file/format/toml.rs64
-rw-r--r--lib/src/file/mod.rs75
-rw-r--r--lib/src/file/source/file.rs129
-rw-r--r--lib/src/file/source/mod.rs12
-rw-r--r--lib/src/file/source/string.rs21
-rw-r--r--lib/src/lib.rs25
-rw-r--r--lib/src/path/mod.rs43
-rw-r--r--lib/src/path/parser.rs (renamed from src/path.rs)28
-rw-r--r--lib/src/source.rs9
-rw-r--r--lib/src/value.rs215
-rw-r--r--src/config.rs709
-rw-r--r--src/env.rs60
-rw-r--r--src/file/json.rs87
-rw-r--r--src/file/mod.rs219
-rw-r--r--src/file/nil.rs13
-rw-r--r--src/file/toml.rs74
-rw-r--r--src/file/yaml.rs101
-rw-r--r--src/lib.rs89
-rw-r--r--src/source.rs12
-rw-r--r--src/value.rs157
33 files changed, 1258 insertions, 1597 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 0f9fad1..99ff591 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,22 +1,8 @@
-[package]
-name = "config"
-version = "0.4.1"
-description = "Layered configuration system for Rust applications."
-homepage = "https://github.com/mehcode/config-rs"
-repository = "https://github.com/mehcode/config-rs"
-readme = "README.md"
-keywords = ["config", "configuration", "settings", "env", "environment"]
-authors = ["Ryan Leckey <leckey.ryan@gmail.com>"]
-license = "MIT/Apache-2.0"
-
-[features]
-default = ["toml"]
-json = ["serde_json"]
-yaml = ["yaml-rust"]
-
-[dependencies]
-nom = "^2.1"
-
-toml = { version = "0.2.1", optional = true }
-serde_json = { version = "0.9", optional = true }
-yaml-rust = { version = "0.3.5", optional = true }
+[workspace]
+members = [
+ "lib",
+ "examples/basic",
+ # "examples/file-json",
+ "examples/file-toml",
+ # "examples/file-yaml",
+]
diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml
index 7ede162..25c3f4d 100644
--- a/examples/basic/Cargo.toml
+++ b/examples/basic/Cargo.toml
@@ -1,6 +1,7 @@
[package]
name = "basic"
version = "0.1.0"
+workspace = "../../"
[dependencies]
-config = { path = "../..", default-features = false }
+config = { path = "../../lib" }
diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs
index 2f947d8..49059ef 100644
--- a/examples/basic/src/main.rs
+++ b/examples/basic/src/main.rs
@@ -1,33 +1,35 @@
extern crate config;
+use config::*;
+
fn main() {
- let mut c = config::Config::new();
+ let mut c = Config::default();
- // 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();
+ // // 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"));
+ // // 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"));
+ // // 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());
+ // // 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"));
+ // // 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
index 7223f35..1e8765e 100644
--- a/examples/file-json/Cargo.toml
+++ b/examples/file-json/Cargo.toml
@@ -1,6 +1,7 @@
[package]
name = "file-json"
version = "0.1.0"
+workspace = "../../"
[dependencies]
-config = { path = "../..", default-features = false, features = ["json"] }
+config = { path = "../../lib", default-features = false, features = ["json"] }
diff --git a/examples/file-toml/Cargo.toml b/examples/file-toml/Cargo.toml
index 6f45799..0501895 100644
--- a/examples/file-toml/Cargo.toml
+++ b/examples/file-toml/Cargo.toml
@@ -1,6 +1,9 @@
[package]
name = "file-toml"
version = "0.1.0"
+workspace = "../../"
[dependencies]
-config = { path = "../.." }
+config = { path = "../../lib", features = ["toml"] }
+serde = "^0.9"
+serde_derive = "^0.9"
diff --git a/examples/file-toml/Settings.toml b/examples/file-toml/Settings.toml
index 28c5fc6..21fa1e3 100644
--- a/examples/file-toml/Settings.toml
+++ b/examples/file-toml/Settings.toml
@@ -1,3 +1,4 @@
-debug = false
+debug = true
pi = 3.14159
weight = 150
+location = { x = 10, y = 30 }
diff --git a/examples/file-toml/src/main.rs b/examples/file-toml/src/main.rs
index 85db701..ddca412 100644
--- a/examples/file-toml/src/main.rs
+++ b/examples/file-toml/src/main.rs
@@ -1,12 +1,22 @@
extern crate config;
+#[macro_use]
+extern crate serde_derive;
+
+#[derive(Debug, Deserialize)]
+struct Point { x: i64, y: i64 }
+
fn main() {
- let mut c = config::Config::new();
+ let mut c = config::Config::default();
// 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"));
+ // Simple key access to values
+ println!("debug = {}", c.get::<bool>("debug").unwrap());
+ println!("pi = {}", c.get::<f64>("pi").unwrap());
+ println!("weight = {}", c.get::<i64>("weight").unwrap());
+ println!("location = {:?}", c.get::<Point>("location").unwrap());
+ // println!("location.x = {}", c.get::<Point>("location.x").unwrap());
+ // println!("location.y = {}", c.get::<Point>("location.y").unwrap());
}
diff --git a/examples/file-yaml/Cargo.toml b/examples/file-yaml/Cargo.toml
index 70176b1..4570078 100644
--- a/examples/file-yaml/Cargo.toml
+++ b/examples/file-yaml/Cargo.toml
@@ -1,6 +1,7 @@
[package]
name = "file-yaml"
version = "0.1.0"
+workspace = "../../"
[dependencies]
-config = { path = "../..", default-features = false, features = ["yaml"] }
+config = { path = "../../lib", default-features = false, features = ["yaml"] }
diff --git a/lib/Cargo.toml b/lib/Cargo.toml
new file mode 100644
index 0000000..6b04a8e
--- /dev/null
+++ b/lib/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "config"
+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"
+readme = "README.md"
+keywords = ["config", "configuration", "settings", "env", "environment"]
+authors = ["Ryan Leckey <leckey.ryan@gmail.com>"]
+license = "MIT/Apache-2.0"
+workspace = "../"
+
+[features]
+default = ["toml", "json", "yaml"]
+json = ["serde_json"]
+yaml = ["yaml-rust"]
+
+[dependencies]
+serde = "^0.9"
+nom = "^2.1"
+
+toml = { version = "^0.3", optional = true }
+serde_json = { version = "^0.9", optional = true }
+yaml-rust = { version = "^0.3.5", optional = true }
diff --git a/lib/src/config.rs b/lib/src/config.rs
new file mode 100644
index 0000000..0767f87
--- /dev/null
+++ b/lib/src/config.rs
@@ -0,0 +1,103 @@
+use std::collections::HashMap;
+use serde::de::Deserialize;
+
+use error::*;
+use source::Source;
+use value::Value;
+use path;
+
+enum ConfigKind {
+ // A mutable configuration. This is the default.
+ Mutable {
+ defaults: HashMap<String, Value>,
+ overrides: HashMap<String, Value>,
+ sources: Vec<Box<Source + Send + Sync>>,
+ },
+
+ // A frozen configuration.
+ // Configuration can no longer be mutated.
+ Frozen,
+}
+
+impl Default for ConfigKind {
+ fn default() -> Self {
+ ConfigKind::Mutable {
+ defaults: HashMap::new(),
+ overrides: HashMap::new(),
+ sources: Vec::new(),
+ }
+ }
+}
+
+/// 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,
+
+ /// Root of the cached configuration.
+ pub cache: Value,
+}
+
+impl Config {
+ /// 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));
+ }
+
+ ConfigKind::Frozen => {
+ return Err(ConfigError::Frozen);
+ }
+ }
+
+ self.refresh()
+ }
+
+ /// 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 {
+ ConfigKind::Mutable { ref overrides, ref sources, ref defaults } => {
+ let mut cache = Value::new(None, HashMap::<String, Value>::new());
+
+ // HACK!
+ cache = sources[0].collect()?;
+
+ cache
+ }
+
+ ConfigKind::Frozen => {
+ return Err(ConfigError::Frozen);
+ }
+ };
+
+ Ok(())
+ }
+
+ pub fn get<T: Deserialize>(&self, key: &str) -> Result<T> {
+ // Parse the key into a path expression
+ let expr: path::Expression = key.to_lowercase().parse()?;
+
+ // Traverse the cache using the path to (possibly) retrieve a value
+ let value = expr.get(&self.cache).cloned();
+
+ match value {
+ Some(value) => {
+ // Deserialize the received value into the requested type
+ T::deserialize(value)
+ }
+
+ None => {
+ Err(ConfigError::NotFound(key.into()))
+ }
+ }
+ }
+}
diff --git a/lib/src/de.rs b/lib/src/de.rs
new file mode 100644
index 0000000..a59d950
--- /dev/null
+++ b/lib/src/de.rs
@@ -0,0 +1,257 @@
+use serde::de;
+use value::{Value, ValueKind};
+use error::*;
+use std::iter::Peekable;
+use std::collections::HashMap;
+use std::collections::hash_map::Drain;
+
+impl de::Deserializer for Value {
+ type Error = ConfigError;
+
+ #[inline]
+ fn deserialize<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ // Deserialize based on the underlying type
+ match self.kind {
+ ValueKind::Boolean(value) => {
+ visitor.visit_bool(value)
+ }
+
+ ValueKind::Table(map) => {
+ visitor.visit_map(MapVisitor::new(map))
+ }
+
+ _ => { unimplemented!(); }
+ }
+ }
+
+ #[inline]
+ fn deserialize_bool<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ visitor.visit_bool(self.into_bool()?)
+ }
+
+ #[inline]
+ fn deserialize_u8<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_u16<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_u32<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_u64<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_i8<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_i16<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_i32<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_i64<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ visitor.visit_i64(self.into_int()?)
+ }
+
+ #[inline]
+ fn deserialize_f32<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_f64<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ visitor.visit_f64(self.into_float()?)
+ }
+
+ #[inline]
+ fn deserialize_char<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_str<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_string<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_bytes<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_byte_buf<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_option<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_unit<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_unit_struct<V: de::Visitor>(self,
+ name: &'static str,
+ visitor: V)
+ -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_newtype_struct<V: de::Visitor>(self,
+ name: &'static str,
+ visitor: V)
+ -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_seq<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_seq_fixed_size<V: de::Visitor>(self,
+ len: usize,
+ visitor: V)
+ -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_tuple<V: de::Visitor>(self, len: usize, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_tuple_struct<V: de::Visitor>(self,
+ name: &'static str,
+ len: usize,
+ visitor: V)
+ -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_enum<V: de::Visitor>(self,
+ name: &'static str,
+ variants: &'static [&'static str],
+ visitor: V)
+ -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ #[inline]
+ fn deserialize_ignored_any<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ unimplemented!();
+ }
+
+ forward_to_deserialize! {
+ map
+ struct
+ struct_field
+ }
+}
+
+struct StrDeserializer<'a>(&'a str);
+
+impl<'a> de::Deserializer for StrDeserializer<'a> {
+ type Error = ConfigError;
+
+ #[inline]
+ fn deserialize<V: de::Visitor>(self, visitor: V) -> Result<V::Value> {
+ visitor.visit_str(self.0)
+ }
+
+ forward_to_deserialize! {
+ bool
+ u8
+ u16
+ u32
+ u64
+ i8
+ i16
+ i32
+ i64
+ f32
+ f64
+ char
+ str
+ string
+ bytes
+ byte_buf
+ option
+ unit
+ unit_struct
+ newtype_struct
+ seq
+ seq_fixed_size
+ tuple
+ tuple_struct
+ map
+ struct
+ struct_field
+ enum
+ ignored_any
+ }
+}
+
+struct MapVisitor {
+ elements: Vec<(String, Value)>,
+ index: usize,
+}
+
+impl MapVisitor {
+ fn new(mut table: HashMap<String, Value>) -> MapVisitor {
+ MapVisitor { elements: table.drain().collect(), index: 0 }
+ }
+}
+
+impl de::MapVisitor for MapVisitor {
+ type Error = ConfigError;
+
+ fn visit_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
+ where K: de::DeserializeSeed,
+ {
+ if self.index >= self.elements.len() {
+ return Ok(None);
+ }
+
+ let ref key_s = self.elements[0].0;
+ let key_de = StrDeserializer(&key_s);
+ let key = de::DeserializeSeed::deserialize(seed, key_de)?;
+
+ Ok(Some(key))
+ }
+
+ fn visit_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
+ where V: de::DeserializeSeed,
+ {
+ de::DeserializeSeed::deserialize(seed, self.elements.remove(0).1)
+ }
+}
diff --git a/lib/src/error.rs b/lib/src/error.rs
new file mode 100644
index 0000000..390057f
--- /dev/null
+++ b/lib/src/error.rs
@@ -0,0 +1,154 @@
+use std::error::Error;
+use std::result;
+use std::fmt;
+use serde::de;
+use nom;
+
+#[derive(Debug)]
+pub enum Unexpected {
+ Bool(bool),
+ Integer(i64),
+ Float(f64),
+ Str(String),
+ Unit,
+ Seq,
+ Map
+}
+
+impl fmt::Display for Unexpected {
+ fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
+ match *self {
+ Unexpected::Bool(b) => write!(f, "boolean `{}`", b),
+ Unexpected::Integer(i) => write!(f, "integer `{}`", i),
+ Unexpected::Float(v) => write!(f, "floating point `{}`", v),
+ Unexpected::Str(ref s) => write!(f, "string {:?}", s),
+ Unexpected::Unit => write!(f, "unit value"),
+ Unexpected::Seq => write!(f, "sequence"),
+ Unexpected::Map => write!(f, "map"),
+ }
+ }
+}
+
+/// Represents all possible errors that can occur when working with
+/// configuration.
+pub enum ConfigError {
+ /// Configuration is frozen and no further mutations can be made.
+ Frozen,
+
+ /// Configuration property was not found
+ NotFound(String),
+
+ /// Configuration path could not be parsed.
+ PathParse(nom::ErrorKind),
+
+ /// Configuration could not be parsed from file.
+ FileParse { uri: Option<String>, cause: Box<Error> },
+
+ /// Value could not be converted into the requested type.
+ Type {
+ origin: Option<String>,
+ unexpected: Unexpected,
+ expected: &'static str,
+ },
+
+ /// Custom message
+ Message(String),
+
+ /// Unadorned error from a foreign source.
+ Foreign(Box<Error>),
+}
+
+impl ConfigError {
+ // FIXME: pub(crate)
+ #[doc(hidden)]
+ pub fn invalid_type(origin: Option<String>, unexpected: Unexpected, expected: &'static str) -> ConfigError {
+ ConfigError::Type {
+ origin: origin,
+ unexpected: unexpected,
+ expected: expected
+ }
+ }
+}
+
+/// Alias for a `Result` with the error type set to `ConfigError`.
+pub type Result<T> = result::Result<T, ConfigError>;
+
+// Forward Debug to Display for readable panic! messages
+impl fmt::Debug for ConfigError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", *self)
+ }
+}
+
+impl fmt::Display for ConfigError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ ConfigError::Frozen | ConfigError::PathParse(_) => {
+ write!(f, "{}", self.description())
+ }
+
+ ConfigError::Message(ref s) => {
+ write!(f, "{}", s)
+ }
+
+ ConfigError::Foreign(ref cause) => {
+ write!(f, "{}", cause)
+ }
+
+ ConfigError::NotFound(ref key) => {
+ write!(f, "configuration property {:?} not found", key)
+ }
+
+ ConfigError::Type { ref origin, ref unexpected, expected } => {
+ write!(f, "invalid type: {}, expected {}",
+ unexpected, expected)?;
+
+ if let Some(ref origin) = *origin {
+ write!(f, " in {}", origin)?;
+ }
+
+ Ok(())
+ }
+
+ ConfigError::FileParse { ref cause, ref uri } => {
+ write!(f, "{}", cause)?;
+
+ if let Some(ref uri) = *uri {
+ write!(f, " in {}", uri)?;
+ }
+
+ Ok(())
+ }
+ }
+ }
+}
+
+impl Error for ConfigError {
+ fn description(&self) -> &str {
+ match *self {
+ ConfigError::Frozen => "configuration is frozen",
+ ConfigError::NotFound(_) => "configuration property not found",
+ ConfigError::Type { .. } => "invalid type",
+ ConfigError::Foreign(ref cause) => cause.description(),
+ ConfigError::FileParse { ref cause, .. } => cause.description(),
+ ConfigError::PathParse(ref kind) => kind.description(),
+
+ _ => "configuration error",
+ }
+ }
+
+ fn cause(&self) -> Option<&Error> {
+ match *self {
+ ConfigError::Foreign(ref cause) => Some(cause.as_ref()),
+ ConfigError::FileParse { ref cause, .. } => Some(cause.as_ref()),
+
+ _ => None
+ }
+ }
+}
+
+impl de::Error for ConfigError {
+ fn custom<T: fmt::Display>(msg: T) -> ConfigError {
+ ConfigError::Message(msg.to_string())
+ }
+}
diff --git a/lib/src/file/format/mod.rs b/lib/src/file/format/mod.rs
new file mode 100644
index 0000000..5c97a7f
--- /dev/null
+++ b/lib/src/file/format/mod.rs
@@ -0,0 +1,60 @@
+use source::Source;
+use value::Value;
+use std::error::Error;
+
+#[cfg(feature = "toml")]
+mod toml;
+
+// #[cfg(feature = "json")]
+// mod json;
+
+// #[cfg(feature = "yaml")]
+// mod yaml;
+
+#[derive(Debug, Clone, Copy)]
+pub enum FileFormat {
+ /// TOML (parsed with toml)
+ #[cfg(feature = "toml")]
+ Toml,
+
+ // /// JSON (parsed with serde_json)
+ // #[cfg(feature = "json")]
+ // Json,
+
+ // /// YAML (parsed with yaml_rust)
+ // #[cfg(feature = "yaml")]
+ // Yaml,
+}
+
+impl FileFormat {
+ // TODO: pub(crate)
+ #[doc(hidden)]
+ pub fn extensions(&self) -> Vec<&'static str> {
+ match *self {
+ #[cfg(feature = "toml")]
+ FileFormat::Toml => vec!["toml"],
+
+ // #[cfg(feature = "json")]
+ // FileFormat::Json => vec!["json"],
+
+ // #[cfg(feature = "yaml")]
+ // FileFormat::Yaml => vec!["yaml", "yml"],
+ }
+ }
+
+ // TODO: pub(crate)
+ #[doc(hidden)]
+ #[allow(unused_variables)]
+ pub fn parse(&self, uri: Option<&String>, text: &str, namespace: Option<&String>) -> Result<Value, Box<Error>> {
+ match *self {
+ #[cfg(feature = "toml")]
+ FileFormat::Toml => toml::parse(uri, text, namespace),
+
+ // #[cfg(feature = "json")]
+ // FileFormat::Json => json::Content::parse(text, namespace),
+
+ // #[cfg(feature = "yaml")]
+ // FileFormat::Yaml => yaml::Content::parse(text, namespace),
+ }
+ }
+}
diff --git a/lib/src/file/format/toml.rs b/lib/src/file/format/toml.rs
new file mode 100644
index 0000000..bbe6aa6
--- /dev/null
+++ b/lib/src/file/format/toml.rs
@@ -0,0 +1,64 @@
+use toml;
+use source::Source;
+use std::collections::{HashMap, BTreeMap};
+use std::error::Error;
+use value::Value;
+
+pub fn parse(uri: Option<&String>, text: &str, namespace: Option<&String>) -> Result<Value, Box<Error>> {
+ // Parse a TOML value from the provided text
+ let mut root: toml::Value = toml::from_str(text)?;
+
+ // Limit to namespace
+ if let Some(namespace) = namespa