summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/config.rs243
-rw-r--r--src/env.rs38
-rw-r--r--src/file/json.rs65
-rw-r--r--src/file/mod.rs123
-rw-r--r--src/file/nil.rs6
-rw-r--r--src/file/toml.rs60
-rw-r--r--src/lib.rs10
-rw-r--r--src/source.rs4
-rw-r--r--src/value.rs65
9 files changed, 387 insertions, 227 deletions
diff --git a/src/config.rs b/src/config.rs
index b3e296b..126d31b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -4,7 +4,7 @@ use source::{Source, SourceBuilder};
use std::error::Error;
use std::fmt;
use std::borrow::Cow;
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
#[derive(Default, Debug)]
pub struct FrozenError { }
@@ -22,10 +22,12 @@ impl Error for FrozenError {
}
// Underlying storage for the configuration
-enum ConfigStore<'a> {
+enum ConfigStore {
Mutable {
- defaults: HashMap<String, Value<'a>>,
- overrides: HashMap<String, Value<'a>>,
+ defaults: HashMap<String, Value>,
+ overrides: HashMap<String, Value>,
+
+ // Ordered list of sources
sources: Vec<Box<Source>>,
},
@@ -34,7 +36,7 @@ enum ConfigStore<'a> {
Frozen,
}
-impl<'a> Default for ConfigStore<'a> {
+impl Default for ConfigStore {
fn default() -> Self {
ConfigStore::Mutable {
defaults: HashMap::new(),
@@ -44,7 +46,60 @@ impl<'a> Default for ConfigStore<'a> {
}
}
-impl<'a> ConfigStore<'a> {
+const KEY_DELIM: char = '.';
+
+fn merge_in(r: &mut HashMap<String, Value>, key: &str, value: &Value) {
+ let key_segments: VecDeque<&str> = key.splitn(2, KEY_DELIM).collect();
+
+ if key_segments.len() > 1 {
+ // Ensure there is at least an empty hash map
+ let key = key_segments[0].to_string();
+ if r.contains_key(&key) {
+ // Coerce to table
+ match *r.get(&key).unwrap() {
+ Value::Table(_) => {
+ // Do nothing; already table
+ }
+
+ _ => {
+ // Override with empty table
+ r.insert(key.clone(), Value::Table(HashMap::new()));
+ }
+ }
+ } else {
+ // Insert table
+ r.insert(key.clone(), Value::Table(HashMap::new()));
+ }
+
+ // Continue to merge
+ if let Value::Table(ref mut table) = *r.get_mut(&key).unwrap() {
+ merge_in(table, key_segments[1], value);
+ }
+
+ return;
+ }
+
+ // Check if we are setting a table (and if we should do a deep merge)
+ if let Value::Table(ref table) = *value {
+ let inner_v = r.get_mut(key);
+ if let Some(&mut Value::Table(ref mut inner_table)) = inner_v {
+ merge_in_all(inner_table, table);
+
+ return;
+ }
+ }
+
+ // Direct set/override whatever is here
+ r.insert(key.into(), value.clone());
+}
+
+fn merge_in_all(r: &mut HashMap<String, Value>, map: &HashMap<String, Value>) {
+ for (key, value) in map {
+ merge_in(r, key, value);
+ }
+}
+
+impl ConfigStore {
fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>>
where T: SourceBuilder
{
@@ -58,10 +113,10 @@ impl<'a> ConfigStore<'a> {
}
fn set_default<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value<'a>>
+ where T: Into<Value>
{
if let ConfigStore::Mutable { ref mut defaults, .. } = *self {
- defaults.insert(key.to_lowercase(), value.into());
+ merge_in(defaults, &key.to_lowercase(), &value.into());
Ok(())
} else {
@@ -70,10 +125,10 @@ impl<'a> ConfigStore<'a> {
}
fn set<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value<'a>>
+ where T: Into<Value>
{
if let ConfigStore::Mutable { ref mut overrides, .. } = *self {
- overrides.insert(key.to_lowercase(), value.into());
+ merge_in(overrides, &key.to_lowercase(), &value.into());
Ok(())
} else {
@@ -81,53 +136,38 @@ impl<'a> ConfigStore<'a> {
}
}
- fn get(&self, key: &str) -> Option<Cow<'a, Value>> {
+ fn collect(&self) -> Result<HashMap<String, Value>, Box<Error>> {
if let ConfigStore::Mutable { ref overrides, ref sources, ref defaults } = *self {
- // Check explicit override
- if let Some(value) = overrides.get(key) {
- return Some(Cow::Borrowed(value));
- }
+ let mut r = HashMap::<String, Value>::new();
- // Check sources
- for source in &mut sources.iter().rev() {
- if let Some(value) = source.get(key) {
- return Some(value);
- }
- }
+ merge_in_all(&mut r, defaults);
- // Check explicit defaults
- if let Some(value) = defaults.get(key) {
- return Some(Cow::Borrowed(value));
+ for source in sources {
+ merge_in_all(&mut r, &source.collect());
}
- }
- None
+ merge_in_all(&mut r, overrides);
+
+ Ok(r)
+ } else {
+ Err(FrozenError::default().into())
+ }
}
}
#[derive(Default)]
-pub struct Config<'a> {
- store: ConfigStore<'a>,
+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>,
}
-// TODO(@rust): There must be a way to remove this function and use Value::as_str
-#[allow(needless_lifetimes)]
-fn value_into_str<'a>(value: Value<'a>) -> Option<Cow<'a, str>> {
- if let Value::String(value) = value {
- Some(value)
- } else if let Value::Integer(value) = value {
- Some(Cow::Owned(value.to_string()))
- } else if let Value::Float(value) = value {
- Some(Cow::Owned(value.to_string()))
- } else if let Value::Boolean(value) = value {
- Some(Cow::Owned(value.to_string()))
- } else {
- None
- }
-}
-
-impl<'a> Config<'a> {
- pub fn new() -> Config<'a> {
+impl Config {
+ pub fn new() -> Self {
Default::default()
}
@@ -135,73 +175,73 @@ impl<'a> Config<'a> {
pub fn merge<T>(&mut self, source: T) -> Result<(), Box<Error>>
where T: SourceBuilder
{
- self.store.merge(source)
+ 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<'a>>
+ where T: Into<Value>
{
- self.store.set_default(key, value)
+ self.store.set_default(key, value)?;
+ self.refresh()?;
+
+ Ok(())
}
/// Sets an override for this key.
pub fn set<T>(&mut self, key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value<'a>>
+ where T: Into<Value>
{
- self.store.set(key, value)
- }
+ self.store.set(key, value)?;
+ self.refresh()?;
- pub fn get(&self, key: &str) -> Option<Cow<'a, Value>> {
- self.store.get(key)
+ Ok(())
}
- pub fn get_str(&'a self, key: &str) -> Option<Cow<'a, str>> {
- // TODO(@rust): This is a bit nasty looking; 3x match and requires this
- // odd into_str method
- if let Some(value) = self.get(key) {
- match value {
- Cow::Borrowed(value) => {
- match value.as_str() {
- Some(value) => {
- match value {
- Cow::Borrowed(value) => Some(Cow::Borrowed(value)),
- Cow::Owned(value) => Some(Cow::Owned(value)),
- }
- }
+ /// 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()?;
- _ => None,
- }
- }
+ Ok(())
+ }
- Cow::Owned(value) => value_into_str(value),
- }
- } else {
- None
- }
+ pub fn get<'a>(&'a self, key: &str) -> Option<&'a Value> {
+ self.cache.get(key)
+ }
+
+ pub fn get_str<'a>(&'a self, key: &str) -> Option<Cow<'a, str>> {
+ self.get(key).and_then(Value::as_str)
}
pub fn get_int(&self, key: &str) -> Option<i64> {
- // TODO(@rust): Why doesn't .and_then(Value::as_int) work?
- self.get(key).and_then(|v| v.as_int())
+ self.get(key).and_then(Value::as_int)
}
pub fn get_float(&self, key: &str) -> Option<f64> {
- // TODO(@rust): Why doesn't .and_then(Value::as_float) work?
- self.get(key).and_then(|v| v.as_float())
+ self.get(key).and_then(Value::as_float)
}
pub fn get_bool(&self, key: &str) -> Option<bool> {
- // TODO(@rust): Why doesn't .and_then(Value::as_bool) work?
- self.get(key).and_then(|v| v.as_bool())
+ self.get(key).and_then(Value::as_bool)
+ }
+
+ pub fn get_map<'a>(&'a self, key: &str) -> Option<&'a HashMap<String, Value>> {
+ self.get(key).and_then(Value::as_map)
}
}
#[cfg(test)]
mod test {
- // use std::env;
- use super::Config;
+ use std::collections::HashMap;
+ use super::{Value, Config};
// Retrieval of a non-existent key
#[test]
@@ -351,4 +391,41 @@ mod test {
assert_eq!(c.get_bool("key_10"), Some(false));
assert_eq!(c.get_bool("key_11"), None);
}
+
+ // Deep merge of tables
+ #[test]
+ fn test_merge() {
+ let mut c = Config::new();
+
+ {
+ let mut m = HashMap::new();
+ m.insert("port".into(), Value::Integer(6379));
+ m.insert("address".into(), Value::String("::1".into()));
+
+ c.set("redis", m).unwrap();
+ }
+
+ {
+ let m = c.get_map("redis").unwrap();
+
+ assert_eq!(m.get("port").unwrap().as_int().unwrap(), 6379);
+ assert_eq!(m.get("address").unwrap().as_str().unwrap(), "::1");
+ }
+
+ {
+ let mut m = HashMap::new();
+ m.insert("address".into(), Value::String("::0".into()));
+ m.insert("db".into(), Value::Integer(1));
+
+ c.set("redis", m).unwrap();
+ }
+
+ {
+ let m = c.get_map("redis").unwrap();
+
+ assert_eq!(m.get("port").unwrap().as_int().unwrap(), 6379);
+ assert_eq!(m.get("address").unwrap().as_str().unwrap(), "::0");
+ assert_eq!(m.get("db").unwrap().as_str().unwrap(), "1");
+ }
+ }
}
diff --git a/src/env.rs b/src/env.rs
index 9743722..60e020e 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -1,6 +1,6 @@
use std::env;
use std::error::Error;
-use std::borrow::Cow;
+use std::collections::HashMap;
use source;
use value::Value;
@@ -27,18 +27,32 @@ impl source::SourceBuilder for Environment {
}
impl source::Source for Environment {
- fn get<'a>(&self, key: &str) -> Option<Cow<'a, Value>> {
- let mut env_key = String::new();
-
- // Apply prefix
- if let Some(ref prefix) = self.prefix {
- env_key.push_str(prefix);
- env_key.push('_');
+ fn collect(&self) -> HashMap<String, Value> {
+ // Iterate through environment variables
+ let mut r = HashMap::new();
+
+ // Make prefix pattern
+ let prefix_pat = if let Some(ref prefix) = self.prefix {
+ Some(prefix.clone() + "_".into())
+ } else { None };
+
+ for (key, value) in env::vars() {
+ let mut key = key.to_string();
+
+ // Check if key matches prefix
+ if let Some(ref prefix_pat) = prefix_pat {
+ if key.starts_with(prefix_pat) {
+ // Remove the prefix from the key
+ key = key[prefix_pat.len()..].to_string();
+ } else {
+ // Skip this key
+ continue;
+ }
+ }
+
+ r.insert(key, Value::String(value));
}
- env_key.push_str(&key.to_uppercase());
-
- // Attempt to retreive environment variable and coerce into a Value
- env::var(env_key.clone()).ok().map(Value::from).map(Cow::Owned)
+ r
}
}
diff --git a/src/file/json.rs b/src/file/json.rs
index 5f1c3a2..d193da4 100644
--- a/src/file/json.rs
+++ b/src/file/json.rs
@@ -2,7 +2,7 @@ use serde_json;
use source::Source;
use std::error::Error;
-use std::borrow::Cow;
+use std::collections::HashMap;
use value::Value;
pub struct Content {
@@ -19,50 +19,57 @@ impl Content {
}
}
-fn from_json_value<'a>(value: &serde_json::Value) -> Option<Cow<'a, Value>> {
+fn from_json_value(value: &serde_json::Value) -> Value {
match *value {
serde_json::Value::String(ref value) => {
- Some(Cow::Owned(Value::String(Cow::Borrowed(value))))
+ Value::String(value.clone())
}
serde_json::Value::Number(ref value) => {
if let Some(value) = value.as_i64() {
- Some(Cow::Owned(Value::Integer(value)))
+ Value::Integer(value)
} else if let Some(value) = value.as_f64() {
- Some(Cow::Owned(Value::Float(value)))
+ Value::Float(value)
} else {
- None
+ unreachable!();
}
}
- serde_json::Value::Bool(value) => Some(Cow::Owned(Value::Boolean(value))),
+ serde_json::Value::Bool(value) => Value::Boolean(value),
- _ => None,
- }
-}
+ serde_json::Value::Object(ref table) => {
+ let mut m = HashMap::new();
-impl Source for Content {
- fn get<'a>(&self, key: &str) -> Option<Cow<'a, Value>> {
- // TODO: Key segment iteration is not something that should be here directly
- let key_delim = '.';
- let key_segments = key.split(key_delim);
- let mut json_cursor = &self.root;
- for segment in key_segments {
- match *json_cursor {
- serde_json::Value::Object(ref table) => {
- if let Some(value) = table.get(segment) {
- json_cursor = value;
- }
- }
+ for (key, value) in table {
+ m.insert(key.clone(), from_json_value(value));
+ }
+
+ Value::Table(m)
+ }
+
+ serde_json::Value::Array(ref array) => {
+ let mut l = Vec::new();
- _ => {
- // This is not a table or array
- // Traversal is not possible
- return None;
- }
+ for value in array {
+ l.push(from_json_value(value));
}
+
+ Value::Array(l)
}
- from_json_value(json_cursor)
+ // TODO: What's left is JSON Null; how should we handle that?
+ _ => { unimplemented!(); }
+ }
+}
+
+impl Source for Content {
+ fn collect(&self) -> HashMap<String, Value> {
+ if let Value::Table(table) = from_json_value(&self.root) {
+ table
+ } else {
+ // TODO: Better handle a non-object at root
+ // NOTE: I never want to support that but a panic is bad
+ panic!("expected object at JSON root");
+ }
}
}
diff --git a/src/file/mod.rs b/src/file/mod.rs
index 6b9fd5c..e85d082 100644
--- a/src/file/mod.rs
+++ b/src/file/mod.rs
@@ -14,6 +14,7 @@ mod toml;
#[cfg(feature = "json")]
mod json;
+#[derive(Clone, Copy)]
pub enum FileFormat {
/// TOML (parsed with toml)
#[cfg(feature = "toml")]
@@ -47,53 +48,34 @@ impl FileFormat {
}
}
-pub struct File {
+pub trait FileSource {
+ fn try_build(&self, format: FileFormat) -> Result<Box<Source>, Box<Error>>;
+}
+
+pub struct FileSourceString(String);
+
+impl FileSource for FileSourceString {
+ fn try_build(&self, format: FileFormat) -> Result<Box<Source>, Box<Error>> {
+ format.parse(&self.0)
+ }
+}
+
+pub struct FileSourceFile {
/// Basename of configuration file
name: String,
/// Directory where configuration file is found
/// When not specified, the current working directory (CWD) is considered
path: Option<String>,
-
- /// Namespace to restrict configuration from the file
- namespace: Option<String>,
-
- /// Format of file (which dictates what driver to use); Defauts to TOML.
- format: FileFormat,
-
- /// A required File will error if it cannot be found
- required: bool,
}
-impl File {
- pub fn new(name: &str, format: FileFormat) -> File {
- File {
- name: name.into(),
- format: format,
- required: true,
- path: None,
- namespace: None,
- }
- }
-
- pub fn path(self, path: &str) -> File {
- File { path: Some(path.into()), ..self }
- }
-
- pub fn namespace(self, namespace: &str) -> File {
- File { namespace: Some(namespace.into()), ..self }
- }
-
- pub fn required(self, required: bool) -> File {
- File { required: required, ..self }
- }
-
+impl FileSourceFile {
// Find configuration file
// Use algorithm similar to .git detection by git
- fn find_file(&self) -> Result<PathBuf, Box<Error>> {
+ fn find_file(&self, format: FileFormat) -> Result<PathBuf, Box<Error>> {
// Build expected configuration file
let mut basename = PathBuf::new();
- let extensions = self.format.extensions();
+ let extensions = format.extensions();
if let Some(ref path) = self.path {
basename.push(path.clone());
@@ -125,11 +107,12 @@ impl File {
}
}
}
+}
- // Build normally and return error on failure
- fn try_build(&self) -> Result<Box<Source>, Box<Error>> {
+impl FileSource for FileSourceFile {
+ fn try_build(&self, format: FileFormat) -> Result<Box<Source>, Box<Error>> {
// Find file
- let filename = self.find_file()?;
+ let filename = self.find_file(format)?;
// Read contents from file
let mut file = fs::File::open(filename)?;
@@ -137,11 +120,71 @@ impl File {
file.read_to_string(&mut text)?;
// Parse the file
- self.format.parse(&text)
+ format.parse(&text)
+ }
+}
+
+pub struct File<T: FileSource> {
+ /// Source of the file
+ source: T,
+
+ /// Namespace to restrict configuration from the file
+ namespace: Option<String>,
+
+ /// Format of file (which dictates what driver to use); Defauts to TOML.
+ format: FileFormat,
+
+ /// A required File will error if it cannot be found
+ required: bool,
+}
+
+impl File<FileSourceString> {
+ pub fn from_str(s: &str, format: FileFormat) -> File<FileSourceString> {
+ File {
+ format: format,
+ required: true,
+ namespace: None,
+ source: FileSourceString(s.into()),
+ }
+ }
+}
+
+impl File<FileSourceFile> {
+ pub fn new(name: &str, format: FileFormat) -> File<FileSourceFile> {
+ File {
+ format: format,
+ required: true,
+ namespace: None,
+ source: FileSourceFile {
+ name: name.into(),
+ path: None,
+ }
+ }
+ }
+}
+
+impl<T: FileSource> File<T> {
+ pub fn required(self, required: bool) -> File<T> {
+ File { required: required, ..self }
+ }
+
+ // Build normally and return error on failure
+ fn try_build(&self) -> Result<Box<Source>, Box<Error>> {
+ self.source.try_build(self.format)
+ }
+}
+
+impl File<FileSourceFile> {
+ pub fn path(self, path: &str) -> Self {
+ File { source: FileSourceFile { path: Some(path.into()), ..self.source } , ..self }
+ }
+
+ pub fn namespace(self, namespace: &str) -> Self {
+ File { namespace: Some(namespace.into()), ..self }
}
}
-impl SourceBuilder for File {
+impl<T: FileSource> SourceBuilder for File<T> {
// Use try_build but only pass an error through if this source
// is required
fn build(&self) -> Result<Box<Source>, Box<Error>> {
diff --git a/src/file/nil.rs b/src/file/nil.rs
index f494af4..f6d801a 100644
--- a/src/file/nil.rs
+++ b/src/file/nil.rs
@@ -1,4 +1,4 @@
-use std::borrow::Cow;
+use std::collections::HashMap;
use source::Source;
use value::Value;
@@ -7,7 +7,7 @@ use value::Value;
pub struct Nil {}
impl Source for Nil {
- fn get<'a>(&self, _: &str) -> Option<Cow<'a, Value>> {
- None
+ fn collect(&self) -> HashMap<String, Value> {
+ HashMap::new()
}
}
diff --git a/src/file/toml.rs b/src/file/toml.rs
index b2a8fe8..4de23ef 100644
--- a/src/file/toml.rs
+++ b/src/file/toml.rs
@@ -1,6 +1,6 @@
use toml;
use source::Source;
-use std::borrow::Cow;
+use std::collections::HashMap;
use std::error::Error;
use value::Value;
@@ -20,39 +20,43 @@ impl Content {
}
}
-fn from_toml_value<'a>(value: &toml::Value) -> Option<Cow<'a, Value>> {
+fn from_toml_value(value: &toml::Value) -> Value {
match *value {
- toml::Value::String(ref value) => Some(Cow::Owned(Value::String(Cow::Borrowed(value)))),
- toml::Value::Float(value) => Some(Cow::Owned(Value::Float(value))),
- toml::Value::Integer(value) => Some(Cow::Owned(Value::Integer(value))),
- toml::Value::Boolean(value) => Some(Cow::Owned(Value::Boolean(value))),
+ toml::Value::String(ref value) => Value::String(value.clone()),
+ toml::Value::Float(value) => Value::Float(value),
+ toml::Value::Integer(value) => Value::Integer(value),
+ toml::Value::Boolean(value) => Value::Boolean(value),
- _ => None,
+ toml::Value::Table(ref table) => {
+ let mut m = HashMap::new();
+
+ for (key, value) in table {
+ m.insert(key.clone(), from_toml_value(value));
+ }
+
+ Value::Table(m)
+ }
+
+ toml::Value::Array(ref array) => {
+ let mut l = Vec::new();
+
+ for value in array {
+ l.push(from_toml_value(value));
+ }
+
+ Value::Array(l)
+ }
+
+ _ => { unimplemented!(); }
}
}
impl Source for Content {
- fn get<'a>(&self, key: &str) -> Option<Cow<'a, Value>> {
- // TODO: Key segment iteration is not something that should be here directly
- let key_delim = '.';
- let key_segments = key.split(key_delim);
- let mut toml_cursor = &self.root;
- for segment in key_segments {
- match *toml_cursor {
- toml::Value::Table(ref table) => {
- if let Some(value) = table.get(segment) {
- toml_cursor = value;
- }
- }
-
- _ => {
- // This is not a table or array
- // Traversal is not possible
- return None;
- }
- }
+ fn collect(&self) -> HashMap<String, Value> {
+ if let Value::Table(table) = from_toml_value(&self.root) {
+ table
+ } else {
+ unreachable!();
}
-
- from_toml_value(toml_cursor)
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 8af61c3..5f8957a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -65,11 +65,11 @@ pub use value::Value;
pub use config::Config;
// Global configuration
-static mut CONFIG: Option<RwLock<Config<'static>>> = None;
+static mut CONFIG: Option<RwLock<Config>> = None;
static CONFIG_INIT: Once = ONCE_INIT;
// Get the global configuration instance
-pub fn global() -> &'static mut RwLock<Config<'static>> {
+pub fn global() -> &'static mut RwLock<Config> {
unsafe {
CONFIG_INIT.call_once(|| {
CONFIG = Some(Default::default());
@@ -86,18 +86,18 @@ pub fn merge<T>(source: T) -> Result<(), Box<Error>>
}
pub fn set_default<T>(key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value<'static>>
+ where T: Into<Value>
{
global().write().unwrap().set_default(key, value)
}
pub fn set<T>(key: &str, value: T) -> Result<(), Box<Error>>
- where T: Into<Value<'static>>
+ where T: Into<Value>
{
global().write().unwrap().set(key, value)
}
-pub fn get<'a>(key: &str) -> Option<Cow<'a, Value>> {
+pub fn get<'a>(key: &str) -> Option<&'a Value> {
// TODO(~): Should this panic! or return None with an error message?
// Make an issue if you think it should be an error message.
let r = global().read().unwrap();
diff --git a/src/source.rs b/src/source.rs
index bc1dd40..e136176 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -1,10 +1,10 @@
use std::error::Error;
-use std::borrow::Cow;
+use std::collections::HashMap;
use value::Value;
pub trait Source {
- fn get<'a>(&self, key: &str) -> Option<Cow<'a, Value>>;
+ fn collect(&self) -> HashMap<String, Value>;
}
pub trait SourceBuilder {
diff --git a/src/value.rs b/src/value.rs
index b66c721..5228d91 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -6,26 +6,27 @@ use std::borrow::Cow;
///
/// Has an underlying or native type that comes from the configuration source
/// but will be coerced into the requested type.
-#[derive(Debug, Clone)]
-pub enum Value<'a> {
- String(Cow<'a, str>),
+#[derive(Debug, Clone, PartialEq)]
+pub enum Value {
+ String(String),
Integer(i64),
Float(f64),
Boolean(bool),
- Table(HashMap<String, Value<'a>>),
- Array(Vec<Value<'a>>),
+ Table(HashMap<String, Value>),
+ Array(Vec<Value>),
}
-impl<'a> Value<'a> {
+impl Value {
/// Gets the underlying value as a string, performing a conversion only if neccessary.
- pub fn as_str(&'a self) -> Option<Cow<'a, str>> {
+ #[allow(needless_lifetimes)]
+ pub fn as_str<'a>(&'a self) -> Option<Cow<'a, str>> {
match *self {
- Value::String(ref value) => Some(Cow::Borrowed(&*value)),
- Value::Integer(value) => Some(value.to_string().into()),
- Value::Float(value) => Some(value.to_string().into()),
- Value::Boolean(value) => Some(value.to_string().into()),
+ Value::String(ref value) => Some(Cow::Borrowed(value)),
+ Value::Integer(value) => Some(Cow::Owned(value.to_string())),
+ Value::Float(value) => Some(Cow::Owned(value.to_string())),
+ Value::Boolean(value) => Some(Cow::Owned(value.to_string())),
- _ => unimplemented!(),
+ _ => None,
}
}
@@ -44,7 +45,7 @@ impl<'a> Value<'a> {
}
}
- _ => unimplemented!(),
+ _ => None,
}
}
@@ -56,7 +57,7 @@ impl<'a> Value<'a> {
Value::Boolean(value) => Some(if value { 1 } else { 0 }),
Value::Float(value) => Some(value.round() as i64),
- _ => unimplemented!(),
+ _ => None,
}
}
@@ -68,7 +69,15 @@ impl<'a> Value<'a> {
Value::Integer(value) => Some(value as f64),
Value::Boolean(value) => Some(if value { 1.0 } else { 0.0 }),
- _ => unimplemented!(),
+ _ => None,
+ }
+ }
+
+ /// Gets the underlying type as a map; only works if the type is actually a map.
+ pub fn as_map(&self) -> Option<&HashMap<String, Value>> {
+ match *self {
+ Value::Table(ref value) => Some(value),
+ _ => None
}
}
}
@@ -76,32 +85,38 @@ impl<'a> Value<'a> {
// Generalized construction from type into variant is needed
// for setting configuration values
-impl<'a> From<String> for Value<'a> {
- fn from(value: String) -> Value<'a> {
+impl From<String> for Value {
+ fn from(value: String) -> Value {
Value::String(value.into())
}
}
-impl<'a> From<&'a str> for Value<'a> {
- fn from(value: &'a str) -> Value<'a> {
+impl<'a> From<&'a str> for Value {
+ fn from(value: &'a str) -> Value {
Value::String(value.into())
}
}
-impl<'a> From<i64> for Value<'a> {
- fn from(value: i64) -> Value<'a> {
+impl From<i64> for Value {
+ fn from(value: i64) -> Value {
Value::Integer(value)
}
}
-impl<'a> From<f64> for Value<'a> {
- fn from(value: f64) -> Value<'a> {
+impl From<f64> for Value {
+ fn from(value: f64) -> Value {
Value::Float(value)
}
}
-impl<'a> From<bool> for Value<'a> {
- fn from(value: bool) -> Value<'a> {
+impl From<bool> for Value {
+ fn from(value: bool) -> Value {
Value::Boolean(value)
}
}
+
+impl From<HashMap<String, Value>> for Value {
+ fn from(value: HashMap<String, Value>) -> Value {
+ Value::Table(value)
+ }
+}