summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Leckey <ryan@launchbadge.com>2017-06-22 14:14:29 -0700
committerRyan Leckey <ryan@launchbadge.com>2017-06-22 14:30:51 -0700
commit2b438ed9b53fee5689032f3b5fcdda8d15becd5f (patch)
tree48374e66a6594c7d11d19fe38f7e23df731b52be
parent04d3ee8f70337e4899932b92262fbeec2dbb1bd9 (diff)
Add builder API to Config
-rw-r--r--Cargo.toml2
-rw-r--r--examples/simple/Cargo.toml6
-rw-r--r--examples/simple/Settings.toml3
-rw-r--r--examples/simple/src/main.rs17
-rw-r--r--src/config.rs185
-rw-r--r--src/env.rs13
-rw-r--r--src/file/format/json.rs30
-rw-r--r--src/file/format/mod.rs6
-rw-r--r--src/file/format/toml.rs26
-rw-r--r--src/file/format/yaml.rs34
-rw-r--r--src/file/mod.rs32
-rw-r--r--src/file/source/file.rs17
-rw-r--r--src/file/source/mod.rs7
-rw-r--r--src/file/source/string.rs5
-rw-r--r--src/source.rs11
15 files changed, 300 insertions, 94 deletions
diff --git a/Cargo.toml b/Cargo.toml
index cec0534..98b82be 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "config"
-version = "0.5.2-pre"
+version = "0.6.0-pre"
description = "Layered configuration system for Rust applications."
homepage = "https://github.com/mehcode/config-rs"
repository = "https://github.com/mehcode/config-rs"
diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml
new file mode 100644
index 0000000..196f742
--- /dev/null
+++ b/examples/simple/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "simple"
+version = "0.1.0"
+
+[dependencies]
+config = { path = "../../" }
diff --git a/examples/simple/Settings.toml b/examples/simple/Settings.toml
new file mode 100644
index 0000000..fd6d3c6
--- /dev/null
+++ b/examples/simple/Settings.toml
@@ -0,0 +1,3 @@
+debug = false
+priority = 32
+key = "189rjfadoisfj8923fjio"
diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs
new file mode 100644
index 0000000..debad02
--- /dev/null
+++ b/examples/simple/src/main.rs
@@ -0,0 +1,17 @@
+extern crate config;
+
+use std::collections::HashMap;
+
+fn main() {
+ let settings = config::Config::default()
+ // Add in `./Settings.toml`
+ .merge(config::File::with_name("Settings"))
+ // Add in settings from the environment (with a prefix of APP)
+ // Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key
+ .merge(config::Environment::with_prefix("APP"))
+ .unwrap();
+
+ // Print out our settings (as a HashMap)
+ println!("{:?}",
+ settings.deserialize::<HashMap<String, String>>().unwrap());
+}
diff --git a/src/config.rs b/src/config.rs
index 44c51d1..c8fc2e4 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,5 +1,7 @@
use std::collections::HashMap;
+use std::ops::Deref;
use std::str::FromStr;
+use std::fmt::Debug;
use serde::de::Deserialize;
use error::*;
@@ -8,6 +10,7 @@ use source::Source;
use value::{Value, ValueWithKey};
use path;
+#[derive(Clone, Debug)]
enum ConfigKind {
// A mutable configuration. This is the default.
Mutable {
@@ -34,7 +37,7 @@ impl Default for ConfigKind {
/// 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)]
+#[derive(Default, Clone, Debug)]
pub struct Config {
kind: ConfigKind,
@@ -48,7 +51,7 @@ impl Config {
}
/// Merge in a configuration property source.
- pub fn merge<T>(&mut self, source: T) -> Result<()>
+ pub fn merge<T>(&mut self, source: T) -> ConfigResult
where T: 'static,
T: Source + Send + Sync
{
@@ -58,7 +61,7 @@ impl Config {
}
ConfigKind::Frozen => {
- return Err(ConfigError::Frozen);
+ return ConfigResult(Err(ConfigError::Frozen));
}
}
@@ -70,7 +73,7 @@ impl Config {
///
/// Configuration is automatically refreshed after a mutation
/// operation (`set`, `merge`, `set_default`, etc.).
- pub fn refresh(&mut self) -> Result<()> {
+ pub fn refresh(&mut self) -> ConfigResult {
self.cache = match self.kind {
// TODO: We need to actually merge in all the stuff
ConfigKind::Mutable {
@@ -87,14 +90,23 @@ impl Config {
// Add sources
for source in sources {
- let props = source.collect()?;
+ let props = match source.collect() {
+ Ok(props) => props,
+ Err(error) => {
+ return ConfigResult(Err(error));
+ }
+ };
+
for (key, val) in &props {
match path::Expression::from_str(key) {
// Set using the path
Ok(expr) => expr.set(&mut cache, val.clone()),
// Set diretly anyway
- _ => path::Expression::Identifier(key.clone()).set(&mut cache, val.clone())
+ _ => {
+ path::Expression::Identifier(key.clone())
+ .set(&mut cache, val.clone())
+ }
}
}
}
@@ -105,14 +117,14 @@ impl Config {
}
cache
- },
+ }
ConfigKind::Frozen => {
- return Err(ConfigError::Frozen);
+ return ConfigResult(Err(ConfigError::Frozen));
}
};
- Ok(())
+ ConfigResult(Ok(self))
}
/// Deserialize the entire configuration.
@@ -120,39 +132,41 @@ impl Config {
T::deserialize(self.cache.clone())
}
- pub fn set_default<T>(&mut self, key: &str, value: T) -> Result<()>
+ pub fn set_default<T>(&mut self, key: &str, value: T) -> ConfigResult
where T: Into<Value>
{
match self.kind {
- ConfigKind::Mutable {
- ref mut defaults,
- ..
- } => {
- defaults.insert(key.to_lowercase().parse()?, value.into());
+ ConfigKind::Mutable { ref mut defaults, .. } => {
+ defaults.insert(match key.to_lowercase().parse() {
+ Ok(expr) => expr,
+ Err(error) => {
+ return ConfigResult(Err(error));
+ }
+ },
+ value.into());
}
- ConfigKind::Frozen => {
- return Err(ConfigError::Frozen)
- }
+ ConfigKind::Frozen => return ConfigResult(Err(ConfigError::Frozen)),
};
self.refresh()
}
- pub fn set<T>(&mut self, key: &str, value: T) -> Result<()>
+ pub fn set<T>(&mut self, key: &str, value: T) -> ConfigResult
where T: Into<Value>
{
match self.kind {
- ConfigKind::Mutable {
- ref mut overrides,
- ..
- } => {
- overrides.insert(key.to_lowercase().parse()?, value.into());
+ ConfigKind::Mutable { ref mut overrides, .. } => {
+ overrides.insert(match key.to_lowercase().parse() {
+ Ok(expr) => expr,
+ Err(error) => {
+ return ConfigResult(Err(error));
+ }
+ },
+ value.into());
}
- ConfigKind::Frozen => {
- return Err(ConfigError::Frozen)
- }
+ ConfigKind::Frozen => return ConfigResult(Err(ConfigError::Frozen)),
};
self.refresh()
@@ -199,3 +213,120 @@ impl Config {
self.get(key).and_then(Value::into_array)
}
}
+
+pub struct ConfigResult<'a>(Result<&'a mut Config>);
+
+#[inline]
+fn unwrap_failed<E: Debug>(msg: &str, error: E) -> ! {
+ panic!("{}: {:?}", msg, error)
+}
+
+impl<'a> ConfigResult<'a> {
+ pub fn merge<T>(self, source: T) -> ConfigResult<'a>
+ where T: 'static,
+ T: Source + Send + Sync
+ {
+ match self.0 {
+ // If OK, Proceed to nested method
+ Ok(instance) => instance.merge(source),
+
+ // Else, Forward the error
+ error => ConfigResult(error),
+ }
+ }
+
+ pub fn set_default<T>(self, key: &str, value: T) -> ConfigResult<'a>
+ where T: Into<Value>,
+ T: 'static
+ {
+ match self.0 {
+ // If OK, Proceed to nested method
+ Ok(instance) => instance.set_default(key, value),
+
+ // Else, Forward the error
+ error => ConfigResult(error),
+ }
+ }
+
+ pub fn set<T>(self, key: &str, value: T) -> ConfigResult<'a>
+ where T: Into<Value>,
+ T: 'static
+ {
+ match self.0 {
+ // If OK, Proceed to nested method
+ Ok(instance) => instance.set(key, value),
+
+ // Else, Forward the error
+ error => ConfigResult(error),
+ }
+ }
+
+ /// Forwards `Result::is_ok`
+ #[inline]
+ pub fn is_ok(&self) -> bool {
+ match self.0 {
+ Ok(_) => true,
+ Err(_) => false,
+ }
+ }
+
+ /// Forwards `Result::is_err`
+ #[inline]
+ pub fn is_err(&self) -> bool {
+ !self.is_ok()
+ }
+
+ /// Forwards `Result::ok`
+ #[inline]
+ pub fn ok(self) -> Option<Config> {
+ match self.0 {
+ Ok(x) => Some(x.clone()),
+ Err(_) => None,
+ }
+ }
+
+ /// Forwards `Result::err`
+ #[inline]
+ pub fn err(self) -> Option<ConfigError> {
+ match self.0 {
+ Ok(_) => None,
+ Err(x) => Some(x),
+ }
+ }
+
+ /// Forwards `Result::unwrap`
+ #[inline]
+ pub fn unwrap(self) -> Config {
+ match self.0 {
+ Ok(instance) => instance.clone(),
+ Err(error) => unwrap_failed("called `Result::unwrap()` on an `Err` value", error),
+ }
+ }
+
+ /// Forwards `Result::expect`
+ #[inline]
+ pub fn expect(self, msg: &str) -> Config {
+ match self.0 {
+ Ok(instance) => instance.clone(),
+ Err(error) => unwrap_failed(msg, error),
+ }
+ }
+
+ /// Forwards `Result::unwrap_err`
+ #[inline]
+ pub fn unwrap_err(self) -> ConfigError {
+ match self.0 {
+ Ok(t) => unwrap_failed("called `Result::unwrap_err()` on an `Ok` value", t),
+ Err(e) => e,
+ }
+ }
+
+ /// Forwards `Result::expect_err`
+ #[inline]
+ pub fn expect_err(self, msg: &str) -> ConfigError {
+ match self.0 {
+ Ok(t) => unwrap_failed(msg, t),
+ Err(e) => e,
+ }
+ }
+}
diff --git a/src/env.rs b/src/env.rs
index a0b190b..abadea2 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -4,6 +4,7 @@ use error::*;
use source::Source;
use value::{Value, ValueKind};
+#[derive(Clone, Debug)]
pub struct Environment {
/// Optional prefix that will limit access to the environment to only keys that
/// begin with the defined prefix.
@@ -29,7 +30,10 @@ impl Environment {
}
pub fn with_prefix(s: &str) -> Self {
- Environment { prefix: Some(s.into()), ..Environment::default() }
+ Environment {
+ prefix: Some(s.into()),
+ ..Environment::default()
+ }
}
pub fn prefix(&mut self, s: String) -> &mut Self {
@@ -53,6 +57,10 @@ impl Default for Environment {
}
impl Source for Environment {
+ fn clone_into_box(&self) -> Box<Source + Send + Sync> {
+ Box::new((*self).clone())
+ }
+
fn collect(&self) -> Result<HashMap<String, Value>> {
let mut m = HashMap::new();
let uri: String = "the environment".into();
@@ -80,7 +88,8 @@ impl Source for Environment {
// Replace `separator` with `.`
key = key.replace(&self.separator, ".");
- m.insert(key.to_lowercase(), Value::new(Some(&uri), ValueKind::String(value)));
+ m.insert(key.to_lowercase(),
+ Value::new(Some(&uri), ValueKind::String(value)));
}
Ok(m)
diff --git a/src/file/format/json.rs b/src/file/format/json.rs
index 4a94943..290e17d 100644
--- a/src/file/format/json.rs
+++ b/src/file/format/json.rs
@@ -4,25 +4,27 @@ use std::collections::HashMap;
use std::error::Error;
use value::{Value, ValueKind};
-pub fn parse(uri: Option<&String>, text: &str, namespace: Option<&String>) -> Result<HashMap<String, Value>, Box<Error>> {
+pub fn parse(uri: Option<&String>,
+ text: &str,
+ namespace: Option<&String>)
+ -> Result<HashMap<String, Value>, Box<Error>> {
// Parse a JSON object value from the text
let mut root: serde_json::Value = serde_json::from_str(text)?;
// Limit to namespace
if let Some(namespace) = namespace {
root = serde_json::Value::Object(match root {
- serde_json::Value::Object(ref mut table) => {
- if let Some(serde_json::Value::Object(table)) = table.remove(namespace) {
- table
- } else {
- serde_json::Map::new()
- }
- }
+ serde_json::Value::Object(ref mut table) => {
+ if let Some(serde_json::Value::Object(table)) =
+ table.remove(namespace) {
+ table
+ } else {
+ serde_json::Map::new()
+ }
+ }
- _ => {
- serde_json::Map::new()
- }
- });
+ _ => serde_json::Map::new(),
+ });
};
// TODO: Have a proper error fire if the root of a file is ever not a Table
@@ -70,8 +72,6 @@ fn from_json_value(uri: Option<&String>, value: &serde_json::Value) -> Value {
Value::new(uri, ValueKind::Array(l))
}
- serde_json::Value::Null => {
- Value::new(uri, ValueKind::Nil)
- }
+ serde_json::Value::Null => Value::new(uri, ValueKind::Nil),
}
}
diff --git a/src/file/format/mod.rs b/src/file/format/mod.rs
index 287b9b4..3fc15a6 100644
--- a/src/file/format/mod.rs
+++ b/src/file/format/mod.rs
@@ -55,7 +55,11 @@ impl FileFormat {
// TODO: pub(crate)
#[doc(hidden)]
#[allow(unused_variables)]
- pub fn parse(&self, uri: Option<&String>, text: &str, namespace: Option<&String>) -> Result<HashMap<String, Value>, Box<Error>> {
+ pub fn parse(&self,
+ uri: Option<&String>,
+ text: &str,
+ namespace: Option<&String>)
+ -> Result<HashMap<String, Value>, Box<Error>> {
match *self {
#[cfg(feature = "toml")]
FileFormat::Toml => toml::parse(uri, text, namespace),
diff --git a/src/file/format/toml.rs b/src/file/format/toml.rs
index 633f39e..2df1fca 100644
--- a/src/file/format/toml.rs
+++ b/src/file/format/toml.rs
@@ -4,25 +4,27 @@ use std::collections::{HashMap, BTreeMap};
use std::error::Error;
use value::{Value, ValueKind};
-pub fn parse(uri: Option<&String>, text: &str, namespace: Option<&String>) -> Result<HashMap<String, Value>, Box<Error>> {
+pub fn parse(uri: Option<&String>,
+ text: &str,
+ namespace: Option<&String>)
+ -> Result<HashMap<String, 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) = namespace {
root = toml::Value::Table(match root {
- toml::Value::Table(ref mut table) => {
- if let Some(toml::Value::Table(table)) = table.remove(namespace) {
- table
- } else {
- BTreeMap::new()
- }
- }
+ toml::Value::Table(ref mut table) => {
+ if let Some(toml::Value::Table(table)) =
+ table.remove(namespace) {
+ table
+ } else {
+ BTreeMap::new()
+ }
+ }
- _ => {
- BTreeMap::new()
- }
- });
+ _ => BTreeMap::new(),
+ });
}
// TODO: Have a proper error fire if the root of a file is ever not a Table
diff --git a/src/file/format/yaml.rs b/src/file/format/yaml.rs
index 7c3dd71..b1afce7 100644
--- a/src/file/format/yaml.rs
+++ b/src/file/format/yaml.rs
@@ -6,7 +6,10 @@ use std::collections::{BTreeMap, HashMap};
use std::mem;
use value::{Value, ValueKind};
-pub fn parse(uri: Option<&String>, text: &str, namespace: Option<&String>) -> Result<HashMap<String, Value>, Box<Error>> {
+pub fn parse(uri: Option<&String>,
+ text: &str,
+ namespace: Option<&String>)
+ -> Result<HashMap<String, Value>, Box<Error>> {
let mut docs = yaml::YamlLoader::load_from_str(text)?;
// Designate root
@@ -21,18 +24,17 @@ pub fn parse(uri: Option<&String>, text: &str, namespace: Option<&String>) -> Re
// Limit to namespace
if let Some(namespace) = namespace {
root = yaml::Yaml::Hash(match root {
- yaml::Yaml::Hash(ref mut table) => {
- if let Some(yaml::Yaml::Hash(table)) = table.remove(&yaml::Yaml::String(namespace.clone())) {
- table
- } else {
- BTreeMap::new()
- }
- }
+ yaml::Yaml::Hash(ref mut table) => {
+ if let Some(yaml::Yaml::Hash(table)) =
+ table.remove(&yaml::Yaml::String(namespace.clone())) {
+ table
+ } else {
+ BTreeMap::new()
+ }
+ }
- _ => {
- BTreeMap::new()
- }
- });
+ _ => BTreeMap::new(),
+ });
};
// TODO: Have a proper error fire if the root of a file is ever not a Table
@@ -47,7 +49,9 @@ pub fn parse(uri: Option<&String>, text: &str, namespace: Option<&String>) -> Re
fn from_yaml_value(uri: Option<&String>, value: &yaml::Yaml) -> Value {
match *value {
yaml::Yaml::String(ref value) => Value::new(uri, ValueKind::String(value.clone())),
- yaml::Yaml::Real(ref value) => Value::new(uri, ValueKind::Float(value.parse::<f64>().unwrap())),
+ yaml::Yaml::Real(ref value) => {
+ Value::new(uri, ValueKind::Float(value.parse::<f64>().unwrap()))
+ }
yaml::Yaml::Integer(value) => Value::new(uri, ValueKind::Integer(value)),
yaml::Yaml::Boolean(value) => Value::new(uri, ValueKind::Boolean(value)),
yaml::Yaml::Hash(ref table) => {
@@ -70,9 +74,7 @@ fn from_yaml_value(uri: Option<&String>, value: &yaml::Yaml) -> Value {
Value::new(uri, ValueKind::Array(l))
}
- yaml::Yaml::Null => {
- Value::new(uri, ValueKind::Nil)
- }
+ yaml::Yaml::Null => Value::new(uri, ValueKind::Nil),
// TODO: how should we BadValue?
_ => {
diff --git a/src/file/mod.rs b/src/file/mod.rs
index 7fc25c8..671f579 100644
--- a/src/file/mod.rs
+++ b/src/file/mod.rs
@@ -10,6 +10,7 @@ use std::path::Path;
use self::source::FileSource;
pub use self::format::FileFormat;
+#[derive(Clone, Debug)]
pub struct File<T>
where T: FileSource
{
@@ -70,12 +71,19 @@ impl<T: FileSource> File<T> {
}
}
-impl<T: FileSource> Source for File<T> {
+impl<T: FileSource> Source for File<T>
+ where T: 'static,
+ T: Sync + Send
+{
+ fn clone_into_box(&self) -> Box<Source + Send + Sync> {
+ Box::new((*self).clone())
+ }
+
fn collect(&self) -> Result<HashMap<String, Value>> {
// Coerce the file contents to a string
- let (uri, contents, format) = match self.source.resolve(self.format).map_err(|err| {
- ConfigError::Foreign(err)
- }) {
+ let (uri, contents, format) = match self.source
+ .resolve(self.format)
+ .map_err(|err| ConfigError::Foreign(err)) {
Ok((uri, contents, format)) => (uri, contents, format),
Err(error) => {
@@ -88,13 +96,13 @@ impl<T: FileSource> Source for File<T> {
};
// Parse the string using the given format
- let result = format.parse(uri.as_ref(), &contents, self.namespace.as_ref()).map_err(|cause| {
- ConfigError::FileParse {
- uri: uri,
- cause: cause
- }
- });
-
- result
+ format
+ .parse(uri.as_ref(), &contents, self.namespace.as_ref())
+ .map_err(|cause| {
+ ConfigError::FileParse {
+ uri: uri,
+ cause: cause,
+ }
+ })
}
}
diff --git a/src/file/source/file.rs b/src/file/source/file.rs
index 79f46bf..426c0b5 100644
--- a/src/file/source/file.rs
+++ b/src/file/source/file.rs
@@ -3,7 +3,7 @@ use std::result;
use std::error::Error;
use std::path::{PathBuf, Path};
-use ::file::format::ALL_EXTENSIONS;
+use file::format::ALL_EXTENSIONS;
use std::io::{self, Read};
use std::fs;
use std::env;
@@ -13,6 +13,7 @@ use source::Source;
use super::{FileFormat, FileSource};
/// Describes a file sourced from a file
+#[derive(Clone, Debug)]
pub struct FileSourceFile {
/// Basename of configuration file
name: String,
@@ -30,7 +31,9 @@ impl FileSourceFile {
}
}
- fn find_file(&self, format_hint: Option<FileFormat>) -> Result<(PathBuf, FileFormat), Box<Error>> {
+ fn find_file(&self,
+ format_hint: Option<FileFormat>)
+ -> Result<(PathBuf, FileFormat), Box<Error>> {
// Build expected configuration file
let mut basename = PathBuf::new();
@@ -47,7 +50,11 @@ impl FileSourceFile {
Some(format) => Ok((filename, format)),
None => {
for (format, extensions) in ALL_EXTENSIONS.iter() {
- if extensions.contains(&filename.extension().unwrap_or_default().to_string_lossy().as_ref()) {
+ if extensions.contains(&filename
+ .extension()
+ .unwrap_or_default()
+ .to_string_lossy()
+ .as_ref()) {
return Ok((filename, *format));
}
}
@@ -90,7 +97,9 @@ impl FileSourceFile {
}
impl FileSource for FileSourceFile {
- fn resolve(&self, format_hint: Option<FileFormat>) -> Result<(Option<String>, String, FileFormat), Box<Error>> {
+ fn resolve(&self,
+ format_hint: Option<FileFormat>)
+ -> Result<(Option<String>, String, FileFormat), Box<Error>> {
// Find file
let (filename, format) = self.find_file(format_hint)?;
diff --git a/src/file/source/mod.rs b/src/file/source/mod.rs
index 89f1a3c..67e2cf1 100644
--- a/src/file/source/mod.rs
+++ b/src/file/source/mod.rs
@@ -1,12 +1,15 @@
pub mod file;
pub mod string;
+use std::fmt::Debug;
use std::error::Error;
use source::Source;
use super::FileFormat;
/// Describes where the file is sourced
-pub trait FileSource {
- fn resolve(&self, format_hint: Option<FileFormat>) -> Result<(Option<String>, String, FileFormat), Box<Error>>;
+pub trait FileSource: Debug + Clone {
+ fn resolve(&self,
+ format_hint: Option<FileFormat>)
+ -> Result<(Option<String>, String, FileFormat), Box<Error>>;
}
diff --git a/src/file/source/string.rs b/src/file/source/string.rs
index 922e46c..70101d6 100644
--- a/src/file/source/string.rs
+++ b/src/file/source/string.rs
@@ -6,6 +6,7 @@ use source::Source;
use super::{FileSource, FileFormat};
/// Describes a file sourced from a string
+#[derive(Clone, Debug)]
pub struct FileSourceString(String);
impl<'a> From<&'a str> for FileSourceString {
@@ -15,7 +16,9 @@ impl<'a> From<&'a str> for FileSourceString {
}
impl FileSource for FileSourceString {
- fn resolve(&self, format_hint: Option<FileFormat>) -> Result<(Option<String>, String, FileFormat), Box<Error>> {
+ fn resolve(&self,
+ format_hint: Option<FileFormat>)
+ -> Result<(Option<String>, String, FileFormat), Box<Error>> {
Ok((None, self.0.clone(), format_hint.expect("from_str requires a set file format")))
}
}
diff --git a/src/source.rs b/src/source.rs
index 5db362e..c8a0e83 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -1,10 +1,19 @@
use error::*;
+use std::fmt::Debug;
use value::Value;
use std::collections::HashMap;
/// Describes a generic _source_ of configuration properties.
-pub trait Source {
+pub trait Source: Debug {
+ fn clone_into_box(&self) -> Box<Source + Send + Sync>;
+
/// Collect all configuration properties available from this source and return
/// a HashMap.
fn collect(&self) -> Result<HashMap<String, Value>>;
}
+
+impl Clone for Box<Source + Send + Sync> {
+ fn clone(&self) -> Box<Source + Send + Sync> {
+ self.clone_into_box()
+ }
+}