summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRyan Leckey <leckey.ryan@gmail.com>2017-01-23 18:58:19 -0800
committerRyan Leckey <leckey.ryan@gmail.com>2017-01-23 18:58:19 -0800
commit40a44a459fde4c4042d5188faedea718a34e2b96 (patch)
tree41dce965b779c29e60504c9f1121286388f0e932 /src
Initial commit.
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs241
-rw-r--r--src/value.rs143
2 files changed, 384 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..0be9063
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,241 @@
+#![feature(try_from)]
+
+mod value;
+
+use value::Value;
+
+use std::env;
+use std::convert::TryFrom;
+use std::collections::HashMap;
+
+#[derive(Default)]
+pub struct Config {
+ defaults: HashMap<String, Value>,
+ overrides: HashMap<String, Value>,
+ environ: HashMap<String, Value>,
+}
+
+impl Config {
+ pub fn new() -> Config {
+ Default::default()
+ }
+
+ pub fn set_default<T>(&mut self, key: &str, value: T)
+ where T: Into<Value>
+ {
+ self.defaults.insert(key.into(), value.into());
+ }
+
+ pub fn set<T>(&mut self, key: &str, value: T)
+ where T: Into<Value>
+ {
+ self.overrides.insert(key.into(), value.into());
+ }
+
+ pub fn get<'a, T>(&'a mut self, key: &str) -> Option<T>
+ where T: TryFrom<&'a mut Value>,
+ T: Default
+ {
+ if let Some(value) = self.overrides.get_mut(key) {
+ T::try_from(value).ok()
+ } else if let Ok(value) = env::var(key.to_uppercase()) {
+ // Store the environment variable into an environ
+ // hash map; we want to return references
+
+ // TODO: Key name needs to go through a transform
+ self.environ.insert(key.to_lowercase().into(), value.into());
+
+ T::try_from(self.environ.get_mut(key).unwrap()).ok()
+ } else if let Some(value) = self.defaults.get_mut(key) {
+ T::try_from(value).ok()
+ } else {
+ None
+ }
+ }
+
+ pub fn get_str<'a>(&'a mut self, key: &str) -> Option<&'a str> {
+ self.get(key)
+ }
+
+ pub fn get_int(&mut self, key: &str) -> Option<i64> {
+ self.get(key)
+ }
+
+ pub fn get_float(&mut self, key: &str) -> Option<f64> {
+ self.get(key)
+ }
+
+ pub fn get_bool(&mut self, key: &str) -> Option<bool> {
+ self.get(key)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::env;
+
+ // Retrieval of a non-existent key
+ #[test]
+ fn test_not_found() {
+ let mut c = super::Config::new();
+
+ assert_eq!(c.get_int("key"), None);
+ }
+
+ // Environment override
+ #[test]
+ fn test_env_override() {
+ let mut c = super::Config::new();
+
+ c.set_default("key_1", false);
+
+ env::set_var("KEY_1", "1");
+
+ assert_eq!(c.get_bool("key_1"), Some(true));
+ }
+
+ // Explicit override
+ #[test]
+ fn test_default_override() {
+ let mut c = super::Config::new();
+
+ c.set_default("key_1", false);
+ c.set_default("key_2", false);
+
+ assert!(!c.get_bool("key_1").unwrap());
+ assert!(!c.get_bool("key_2").unwrap());
+
+ c.set("key_2", true);
+
+ assert!(!c.get_bool("key_1").unwrap());
+ assert!(c.get_bool("key_2").unwrap());
+ }
+
+ // Storage and retrieval of String values
+ #[test]
+ fn test_str() {
+ let mut c = super::Config::new();
+
+ c.set("key", "value");
+
+ assert_eq!(c.get_str("key").unwrap(), "value");
+ assert!("value" == c.get::<&str>("key").unwrap());
+ }
+
+ // Storage and retrieval of Boolean values
+ #[test]
+ fn test_bool() {
+ let mut c = super::Config::new();
+
+ c.set("key", true);
+
+ assert_eq!(c.get_bool("key").unwrap(), true);
+ assert!(false != c.get("key").unwrap());
+ }
+
+ // Storage and retrieval of Float values
+ #[test]
+ fn test_float() {
+ let mut c = super::Config::new();
+
+ c.set("key", 3.14);
+
+ assert_eq!(c.get_float("key").unwrap(), 3.14);
+ assert!(3.14 >= c.get("key").unwrap());
+ }
+
+ // Storage and retrieval of Integer values
+ #[test]
+ fn test_int() {
+ let mut c = super::Config::new();
+
+ c.set("key", 42);
+
+ assert_eq!(c.get_int("key").unwrap(), 42);
+ assert!(42 == c.get::<i64>("key").unwrap());
+ }
+
+ // Storage of various values and retrieval as String
+ #[test]
+ fn test_retrieve_str() {
+ let mut c = super::Config::new();
+
+ c.set("key_1", 115);
+ c.set("key_2", 1.23);
+ c.set("key_3", false);
+
+ assert_eq!(c.get_str("key_1"), Some("115"));
+ assert_eq!(c.get_str("key_2"), Some("1.23"));
+ assert_eq!(c.get_str("key_3"), Some("false"));
+ }
+
+ // Storage of various values and retrieval as Integer
+ #[test]
+ fn test_retrieve_int() {
+ let mut c = super::Config::new();
+
+ c.set("key_1", "121");
+ c.set("key_2", 5.12);
+ c.set("key_3", 5.72);
+ c.set("key_4", false);
+ c.set("key_5", true);
+ c.set("key_6", "asga");
+
+ assert_eq!(c.get_int("key_1"), Some(121));
+ assert_eq!(c.get_int("key_2"), Some(5));
+ assert_eq!(c.get_int("key_3"), Some(6));
+ assert_eq!(c.get_int("key_4"), Some(0));
+ assert_eq!(c.get_int("key_5"), Some(1));
+ assert_eq!(c.get_int("key_6"), None);
+ }
+
+ // Storage of various values and retrieval as Float
+ #[test]
+ fn test_retrieve_float() {
+ let mut c = super::Config::new();
+
+ c.set("key_1", "121");
+ c.set("key_2", "121.512");
+ c.set("key_3", 5);
+ c.set("key_4", false);
+ c.set("key_5", true);
+ c.set("key_6", "asga");
+
+ assert_eq!(c.get_float("key_1"), Some(121.0));
+ assert_eq!(c.get_float("key_2"), Some(121.512));
+ assert_eq!(c.get_float("key_3"), Some(5.0));
+ assert_eq!(c.get_float("key_4"), Some(0.0));
+ assert_eq!(c.get_float("key_5"), Some(1.0));
+ assert_eq!(c.get_float("key_6"), None);
+ }
+
+ // Storage of various values and retrieval as Boolean
+ #[test]
+ fn test_retrieve_bool() {
+ let mut c = super::Config::new();
+
+ c.set("key_1", "121");
+ c.set("key_2", "1");
+ c.set("key_3", "0");
+ c.set("key_4", "true");
+ c.set("key_5", "");
+ c.set("key_6", 51);
+ c.set("key_7", 0);
+ c.set("key_8", 12.12);
+ c.set("key_9", 1.0);
+ c.set("key_10", 0.0);
+ c.set("key_11", "asga");
+
+ assert_eq!(c.get_bool("key_1"), Some(false));
+ assert_eq!(c.get_bool("key_2"), Some(true));
+ assert_eq!(c.get_bool("key_3"), Some(false));
+ assert_eq!(c.get_bool("key_4"), Some(true));
+ assert_eq!(c.get_bool("key_5"), Some(false));
+ assert_eq!(c.get_bool("key_6"), Some(true));
+ assert_eq!(c.get_bool("key_7"), Some(false));
+ assert_eq!(c.get_bool("key_8"), Some(true));
+ assert_eq!(c.get_bool("key_9"), Some(true));
+ assert_eq!(c.get_bool("key_10"), Some(false));
+ assert_eq!(c.get_bool("key_11"), Some(false));
+ }
+}
diff --git a/src/value.rs b/src/value.rs
new file mode 100644
index 0000000..295c7c0
--- /dev/null
+++ b/src/value.rs
@@ -0,0 +1,143 @@
+use std::convert::{From, TryFrom};
+
+// Variant for a configuration Value
+// The additional Option<String> is used for the textual representation of the
+// underlying type (to cache the string generation) but only if requested.
+pub enum Value {
+ String(String),
+ Integer(i64, Option<String>),
+ Float(f64, Option<String>),
+ Boolean(bool, Option<String>),
+}
+
+// Conversion from type into variant
+impl From<String> for Value {
+ fn from(value: String) -> Value {
+ Value::String(value)
+ }
+}
+
+impl<'a> From<&'a str> for Value {
+ fn from(value: &'a str) -> Value {
+ Value::String(value.into())
+ }
+}
+
+impl From<i64> for Value {
+ fn from(value: i64) -> Value {
+ Value::Integer(value, None)
+ }
+}
+
+impl From<f64> for Value {
+ fn from(value: f64) -> Value {
+ Value::Float(value, None)
+ }
+}
+
+impl From<bool> for Value {
+ fn from(value: bool) -> Value {
+ Value::Boolean(value, None)
+ }
+}
+
+// Conversion from variant into type
+impl<'a> TryFrom<&'a mut Value> for &'a str {
+ type Err = ();
+
+ fn try_from(value: &mut Value) -> Result<&str, ()> {
+ // When converting a non-string value into a string;
+ // cache the conversion and return a reference
+
+ if let Value::String(ref value) = *value {
+ Ok(value)
+ } else if let Value::Integer(value, ref mut text) = *value {
+ if let Some(ref text) = *text {
+ Ok(text)
+ } else {
+ *text = Some(value.to_string());
+
+ Ok(text.as_ref().unwrap())
+ }
+ } else if let Value::Float(value, ref mut text) = *value {
+ if let Some(ref text) = *text {
+ Ok(text)
+ } else {
+ *text = Some(value.to_string());
+
+ Ok(text.as_ref().unwrap())
+ }
+ } else if let Value::Boolean(value, ref mut text) = *value {
+ if let Some(ref text) = *text {
+ Ok(text)
+ } else {
+ *text = Some(value.to_string());
+
+ Ok(text.as_ref().unwrap())
+ }
+ } else {
+ Err(())
+ }
+ }
+}
+
+impl<'a> TryFrom<&'a mut Value> for i64 {
+ type Err = ();
+
+ fn try_from(value: &mut Value) -> Result<i64, ()> {
+ if let Value::Integer(value, ..) = *value {
+ Ok(value)
+ } else if let Value::String(ref value) = *value {
+ value.parse().map_err(|_| {
+ // Drop specific error
+ })
+ } else if let Value::Boolean(value, ..) = *value {
+ Ok(if value { 1 } else { 0 })
+ } else if let Value::Float(value, ..) = *value {
+ Ok(value.round() as i64)
+ } else {
+ Err(())
+ }
+ }
+}
+
+impl<'a> TryFrom<&'a mut Value> for f64 {
+ type Err = ();
+
+ fn try_from(value: &mut Value) -> Result<f64, ()> {
+ if let Value::Float(value, ..) = *value {
+ Ok(value)
+ } else if let Value::String(ref value) = *value {
+ value.parse().map_err(|_| {
+ // Drop specific error
+ })
+ } else if let Value::Integer(value, ..) = *value {
+ Ok(value as f64)
+ } else if let Value::Boolean(value, ..) = *value {
+ Ok(if value { 1.0 } else { 0.0 })
+ } else {
+ Err(())
+ }
+ }
+}
+
+impl<'a> TryFrom<&'a mut Value> for bool {
+ type Err = ();
+
+ fn try_from(value: &mut Value) -> Result<bool, ()> {
+ if let Value::Boolean(value, ..) = *value {
+ Ok(value)
+ } else if let Value::String(ref value) = *value {
+ Ok(match value.to_lowercase().as_ref() {
+ "1" | "true" | "on" | "yes" => true,
+ _ => false,
+ })
+ } else if let Value::Integer(value, ..) = *value {
+ Ok(value != 0)
+ } else if let Value::Float(value, ..) = *value {
+ Ok(value != 0.0)
+ } else {
+ Err(())
+ }
+ }
+}