diff options
author | Ryan Leckey <ryan@launchbadge.com> | 2017-06-01 23:22:04 -0700 |
---|---|---|
committer | Ryan Leckey <ryan@launchbadge.com> | 2017-06-01 23:22:04 -0700 |
commit | bfc44c331a77d8c341c076e72df5ed0b56fbd422 (patch) | |
tree | c757723957be6b880d1e0d8d26ae2b1c9c606ed2 /src/value.rs | |
parent | 4357840e95f3646494ddeea4aae12425dfab2db8 (diff) |
Move things around and get some tests in place
Diffstat (limited to 'src/value.rs')
-rw-r--r-- | src/value.rs | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..3f659bc --- /dev/null +++ b/src/value.rs @@ -0,0 +1,328 @@ +use std::collections::HashMap; +use std::fmt::Display; +use error::*; + +/// Underlying kind of the configuration value. +#[derive(Debug, Clone)] +pub enum ValueKind { + Nil, + Boolean(bool), + Integer(i64), + Float(f64), + String(String), + Table(Table), + Array(Array), +} + +pub type Array = Vec<Value>; +pub type Table = HashMap<String, Value>; + +impl Default for ValueKind { + fn default() -> Self { + ValueKind::Nil + } +} + +impl<T> From<Option<T>> for ValueKind + where T: Into<ValueKind> +{ + fn from(value: Option<T>) -> Self { + match value { + Some(value) => value.into(), + None => ValueKind::Nil, + } + } +} + +impl From<String> for ValueKind { + fn from(value: String) -> Self { + ValueKind::String(value.into()) + } +} + +impl<'a> From<&'a str> for ValueKind { + fn from(value: &'a str) -> Self { + ValueKind::String(value.into()) + } +} + +impl From<i64> for ValueKind { + fn from(value: i64) -> Self { + ValueKind::Integer(value) + } +} + +impl From<f64> for ValueKind { + fn from(value: f64) -> Self { + ValueKind::Float(value) + } +} + +impl From<bool> for ValueKind { + fn from(value: bool) -> Self { + ValueKind::Boolean(value) + } +} + +impl<T> From<HashMap<String, T>> for ValueKind + where T: Into<Value> +{ + fn from(values: HashMap<String, T>) -> Self { + let mut r = HashMap::new(); + + for (k, v) in values { + r.insert(k.clone(), v.into()); + } + + ValueKind::Table(r) + } +} + +impl<T> From<Vec<T>> for ValueKind + where T: Into<Value> +{ + fn from(values: Vec<T>) -> Self { + let mut l = Vec::new(); + + for v in values { + l.push(v.into()); + } + + ValueKind::Array(l) + } +} + +/// A configuration value. +#[derive(Default, Debug, Clone)] +pub struct Value { + /// A description of the original location of the value. + /// + /// A Value originating from a File might contain: + /// ``` + /// Settings.toml at line 1 column 2 + /// ``` + /// + /// A Value originating from the environment would contain: + /// ``` + /// the envrionment + /// ``` + /// + /// A Value originating from a remote source might contain: + /// ``` + /// etcd+http://127.0.0.1:2379 + /// ``` + origin: Option<String>, + + /// Underlying kind of the configuration value. + pub kind: ValueKind, +} + +impl Value { + pub fn new<V>(origin: Option<&String>, kind: V) -> Self + where V: Into<ValueKind> + { + Value { + origin: origin.cloned(), + kind: kind.into(), + } + } + + /// Returns `self` as a bool, if possible. + pub fn into_bool(self) -> Result<bool> { + match self.kind { + ValueKind::Boolean(value) => Ok(value), + ValueKind::Integer(value) => Ok(value != 0), + ValueKind::Float(value) => Ok(value != 0.0), + + ValueKind::String(ref value) => { + match value.to_lowercase().as_ref() { + "1" | "true" | "on" | "yes" => Ok(true), + "0" | "false" | "off" | "no" => Ok(false), + + // Unexpected string value + s @ _ => { + Err(ConfigError::invalid_type(self.origin.clone(), + Unexpected::Str(s.into()), + "a boolean")) + } + } + } + + // Unexpected type + ValueKind::Nil => { + Err(ConfigError::invalid_type(self.origin.clone(), Unexpected::Unit, "a boolean")) + } + ValueKind::Table(_) => { + Err(ConfigError::invalid_type(self.origin.clone(), Unexpected::Map, "a boolean")) + } + ValueKind::Array(_) => { + Err(ConfigError::invalid_type(self.origin.clone(), Unexpected::Seq, "a boolean")) + } + } + } + + /// Returns `self` into an i64, if possible. + pub fn into_int(self) -> Result<i64> { + match self.kind { + ValueKind::Integer(value) => Ok(value), + + ValueKind::String(ref s) => { + match s.to_lowercase().as_ref() { + "true" | "on" | "yes" => Ok(1), + "false" | "off" | "no" => Ok(0), + _ => { + s.parse().map_err(|_| { + // Unexpected string + ConfigError::invalid_type(self.origin.clone(), + Unexpected::Str(s.clone()), + "an integer") + }) + } + } + } + + ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }), + ValueKind::Float(value) => Ok(value.round() as i64), + + // Unexpected type + ValueKind::Nil => { + Err(ConfigError::invalid_type(self.origin.clone(), Unexpected::Unit, "an integer")) + } + ValueKind::Table(_) => { + Err(ConfigError::invalid_type(self.origin.clone(), Unexpected::Map, "an integer")) + } + ValueKind::Array(_) => { + Err(ConfigError::invalid_type(self.origin.clone(), Unexpected::Seq, "an integer")) + } + } + } + + /// Returns `self` into a f64, if possible. + pub fn into_float(self) -> Result<f64> { + match self.kind { + ValueKind::Float(value) => Ok(value), + + ValueKind::String(ref s) => { + match s.to_lowercase().as_ref() { + "true" | "on" | "yes" => Ok(1.0), + "false" | "off" | "no" => Ok(0.0), + _ => { + s.parse().map_err(|_| { + // Unexpected string + ConfigError::invalid_type(self.origin.clone(), + Unexpected::Str(s.clone()), + "a floating point") + }) + } + } + } + + ValueKind::Integer(value) => Ok(value as f64), + ValueKind::Boolean(value) => Ok(if value { 1.0 } else { 0.0 }), + + // Unexpected type + ValueKind::Nil => { + Err(ConfigError::invalid_type(self.origin.clone(), + Unexpected::Unit, + "a floating point")) + } + ValueKind::Table(_) => { + Err(ConfigError::invalid_type(self.origin.clone(), + Unexpected::Map, + "a floating point")) + } + ValueKind::Array(_) => { + Err(ConfigError::invalid_type(self.origin.clone(), + Unexpected::Seq, + "a floating point")) + } + } + } + + /// Returns `self` into a str, if possible. + pub fn into_str(self) -> Result<String> { + match self.kind { + ValueKind::String(value) => Ok(value), + + ValueKind::Boolean(value) => Ok(value.to_string()), + ValueKind::Integer(value) => Ok(value.to_string()), + ValueKind::Float(value) => Ok(value.to_string()), + + // Cannot convert + ValueKind::Nil => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Unit, "a string")) + } + ValueKind::Table(_) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Map, "a string")) + } + ValueKind::Array(_) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Seq, "a string")) + } + } + } + + /// Returns `self` into an array, if possible + pub fn into_array(self) -> Result<Vec<Value>> { + match self.kind { + ValueKind::Array(value) => Ok(value), + + // Cannot convert + ValueKind::Float(value) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Float(value), "an array")) + } + ValueKind::String(value) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Str(value), "an array")) + } + ValueKind::Integer(value) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Integer(value), "an array")) + } + ValueKind::Boolean(value) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Bool(value), "an array")) + } + ValueKind::Nil => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Unit, "an array")) + } + ValueKind::Table(_) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Map, "an array")) + } + } + } + + /// If the `Value` is a Table, returns the associated Map. + pub fn into_table(self) -> Result<HashMap<String, Value>> { + match self.kind { + ValueKind::Table(value) => Ok(value), + + // Cannot convert + ValueKind::Float(value) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Float(value), "a map")) + } + ValueKind::String(value) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Str(value), "a map")) + } + ValueKind::Integer(value) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Integer(value), "a map")) + } + ValueKind::Boolean(value) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Bool(value), "a map")) + } + ValueKind::Nil => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Unit, "a map")) + } + ValueKind::Array(_) => { + Err(ConfigError::invalid_type(self.origin, Unexpected::Seq, "a map")) + } + } + } +} + +impl<T> From<T> for Value + where T: Into<ValueKind> +{ + fn from(value: T) -> Self { + Value { + origin: None, + kind: value.into(), + } + } +} |