diff options
Diffstat (limited to 'src/value.rs')
-rw-r--r-- | src/value.rs | 571 |
1 files changed, 470 insertions, 101 deletions
diff --git a/src/value.rs b/src/value.rs index 5b5a838..dd48531 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,157 +1,526 @@ -use std::convert::From; use std::collections::HashMap; +use std::fmt::Display; +use std::fmt; +use error::*; +use serde::de::{Deserialize, Deserializer, Visitor}; -/// A configuration value. -/// -/// Has an underlying or native type that comes from the configuration source -/// but will be coerced into the requested type. -#[derive(Debug, Clone, PartialEq)] -pub enum Value { +/// Underlying kind of the configuration value. +#[derive(Debug, Clone)] +pub enum ValueKind { Nil, - String(String), + Boolean(bool), Integer(i64), Float(f64), - Boolean(bool), - Table(HashMap<String, Value>), - Array(Vec<Value>), + String(String), + Table(Table), + Array(Array), } -impl Value { - /// Converts `self` into a string, if possible. - /// Returns None otherwise. - pub fn into_str(self) -> Option<String> { - match self { - Value::String(value) => Some(value), - Value::Integer(value) => Some(value.to_string()), - Value::Float(value) => Some(value.to_string()), - Value::Boolean(value) => Some(value.to_string()), +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(); - _ => None, + for v in values { + l.push(v.into()); } + + ValueKind::Array(l) } +} - /// Converts `self` into a bool, if possible. - /// Returns None otherwise. - pub fn into_bool(self) -> Option<bool> { - match self { - Value::Boolean(value) => Some(value), - Value::Integer(value) => Some(value != 0), - Value::Float(value) => Some(value != 0.0), +/// 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 + /// ``` + /// + /// 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, +} - Value::String(ref value) => { +impl Value { + /// Create a new value instance that will remember its source uri. + pub fn new<V>(origin: Option<&String>, kind: V) -> Self + where V: Into<ValueKind> + { + Value { + origin: origin.cloned(), + kind: kind.into(), + } + } + + /// Attempt to deserialize this value into the requested type. + pub fn try_into<'de, T: Deserialize<'de>>(self) -> Result<T> { + T::deserialize(self) + } + + /// Returns `self` as a bool, if possible. + // FIXME: Should this not be `try_into_*` ? + 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" => Some(true), - "0" | "false" | "off" | "no" => Some(false), - _ => None, + "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")) + } } } - _ => None, + // 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")) + } } } - /// Converts `self` into an i64, if possible. - /// Returns None otherwise. - pub fn into_int(self) -> Option<i64> { - match self { - Value::Integer(value) => Some(value), - Value::String(ref value) => value.parse().ok(), - Value::Boolean(value) => Some(if value { 1 } else { 0 }), - Value::Float(value) => Some(value.round() as i64), + /// Returns `self` into an i64, if possible. + // FIXME: Should this not be `try_into_*` ? + 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), - _ => None, + // 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")) + } } } - /// Converts `self` into a f64, if possible. - /// Returns None otherwise. - pub fn into_float(self) -> Option<f64> { - match self { - Value::Float(value) => Some(value), - Value::String(ref value) => value.parse().ok(), - Value::Integer(value) => Some(value as f64), - Value::Boolean(value) => Some(if value { 1.0 } else { 0.0 }), + /// Returns `self` into a f64, if possible. + // FIXME: Should this not be `try_into_*` ? + pub fn into_float(self) -> Result<f64> { + match self.kind { + ValueKind::Float(value) => Ok(value), - _ => None, + 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")) + } } } - /// If the `Value` is a Table, returns the associated Map. - /// Returns None otherwise. - pub fn into_table(self) -> Option<HashMap<String, Value>> { - match self { - Value::Table(value) => Some(value), - _ => None, + /// Returns `self` into a str, if possible. + // FIXME: Should this not be `try_into_*` ? + 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")) + } } } - /// If the `Value` is an Array, returns the associated Vector. - /// Returns None otherwise. - pub fn into_array(self) -> Option<Vec<Value>> { - match self { - Value::Array(value) => Some(value), - _ => None, + /// Returns `self` into an array, if possible + // FIXME: Should this not be `try_into_*` ? + 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")) + } } } -} -// Generalized construction from type into variant is needed -// for setting configuration values + /// If the `Value` is a Table, returns the associated Map. + // FIXME: Should this not be `try_into_*` ? + pub fn into_table(self) -> Result<HashMap<String, Value>> { + match self.kind { + ValueKind::Table(value) => Ok(value), -impl From<String> for Value { - fn from(value: String) -> Value { - Value::String(value.into()) + // 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<'a> From<&'a str> for Value { - fn from(value: &'a str) -> Value { - Value::String(value.into()) - } -} +impl<'de> Deserialize<'de> for Value { + #[inline] + fn deserialize<D>(deserializer: D) -> ::std::result::Result<Value, D::Error> + where + D: Deserializer<'de>, + { + struct ValueVisitor; + + impl<'de> Visitor<'de> for ValueVisitor { + type Value = Value; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("any valid configuration value") + } + + #[inline] + fn visit_bool<E>(self, value: bool) -> ::std::result::Result<Value, E> { + Ok(value.into()) + } + + #[inline] + fn visit_i8<E>(self, value: i8) -> ::std::result::Result<Value, E> { + Ok((value as i64).into()) + } + + #[inline] + fn visit_i16<E>(self, value: i16) -> ::std::result::Result<Value, E> { + Ok((value as i64).into()) + } + + #[inline] + fn visit_i32<E>(self, value: i32) -> ::std::result::Result<Value, E> { + Ok((value as i64).into()) + } + + #[inline] + fn visit_i64<E>(self, value: i64) -> ::std::result::Result<Value, E> { + Ok(value.into()) + } + + #[inline] + fn visit_u8<E>(self, value: u8) -> ::std::result::Result<Value, E> { + Ok((value as i64).into()) + } + + #[inline] + fn visit_u16<E>(self, value: u16) -> ::std::result::Result<Value, E> { + Ok((value as i64).into()) + } + + #[inline] + fn visit_u32<E>(self, value: u32) -> ::std::result::Result<Value, E> { + Ok((value as i64).into()) + } + + #[inline] + fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Value, E> { + // FIXME: This is bad + Ok((value as i64).into()) + } + + #[inline] + fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Value, E> { + Ok(value.into()) + } + + #[inline] + fn visit_str<E>(self, value: &str) -> ::std::result::Result<Value, E> + where + E: ::serde::de::Error, + { + self.visit_string(String::from(value)) + } + + #[inline] + fn visit_string<E>(self, value: String) -> ::std::result::Result<Value, E> { + Ok(value.into()) + } + + #[inline] + fn visit_none<E>(self) -> ::std::result::Result<Value, E> { + Ok(Value::new(None, ValueKind::Nil)) + } -impl From<i64> for Value { - fn from(value: i64) -> Value { - Value::Integer(value) + #[inline] + fn visit_some<D>(self, deserializer: D) -> ::std::result::Result<Value, D::Error> + where D: Deserializer<'de> + { + Deserialize::deserialize(deserializer) + } + + #[inline] + fn visit_unit<E>(self) -> ::std::result::Result<Value, E> { + Ok(Value::new(None, ValueKind::Nil)) + } + + #[inline] + fn visit_seq<V>(self, mut visitor: V) -> ::std::result::Result<Value, V::Error> + where + V: ::serde::de::SeqAccess<'de>, + { + let mut vec = Array::new(); + + while let Some(elem) = try!(visitor.next_element()) { + vec.push(elem); + } + + Ok(vec.into()) + } + + fn visit_map<V>(self, mut visitor: V) -> ::std::result::Result<Value, V::Error> + where + V: ::serde::de::MapAccess<'de>, + { + let mut values = Table::new(); + + while let Some((key, value)) = try!(visitor.next_entry()) { + values.insert(key, value); + } + + Ok(values.into()) + } + } + + deserializer.deserialize_any(ValueVisitor) } } -impl From<f64> for Value { - fn from(value: f64) -> Value { - Value::Float(value) +impl<T> From<T> for Value + where T: Into<ValueKind> +{ + fn from(value: T) -> Self { + Value { + origin: None, + kind: value.into(), + } } } -impl From<bool> for Value { - fn from(value: bool) -> Value { - Value::Boolean(value) +pub struct ValueWithKey<'a>(pub Value, &'a str); + +impl<'a> ValueWithKey<'a> { + pub fn new(value: Value, key: &'a str) -> Self + { + ValueWithKey(value, key) } -} -impl<T> From<HashMap<String, T>> for Value - where T: Into<Value> -{ - fn from(values: HashMap<String, T>) -> Value { - let mut r = HashMap::new(); + pub fn into_bool(self) -> Result<bool> { + match self.0.into_bool() { + Ok(value) => Ok(value), + Err(error) => Err(error.extend_with_key(self.1)) + } + } - for (k, v) in values { - r.insert(k.clone(), v.into()); + /// Returns `self` into an i64, if possible. + pub fn into_int(self) -> Result<i64> { + match self.0.into_int() { + Ok(value) => Ok(value), + Err(error) => Err(error.extend_with_key(self.1)) } + } - Value::Table(r) + /// Returns `self` into a f64, if possible. + pub fn into_float(self) -> Result<f64> { + match self.0.into_float() { + Ok(value) => Ok(value), + Err(error) => Err(error.extend_with_key(self.1)) + } } -} -impl<T> From<Vec<T>> for Value - where T: Into<Value> -{ - fn from(values: Vec<T>) -> Value { - let mut l = Vec::new(); + /// Returns `self` into a str, if possible. + pub fn into_str(self) -> Result<String> { + match self.0.into_str() { + Ok(value) => Ok(value), + Err(error) => Err(error.extend_with_key(self.1)) + } + } - for v in values { - l.push(v.into()); + /// Returns `self` into an array, if possible + pub fn into_array(self) -> Result<Vec<Value>> { + match self.0.into_array() { + Ok(value) => Ok(value), + Err(error) => Err(error.extend_with_key(self.1)) } + } - Value::Array(l) + /// If the `Value` is a Table, returns the associated Map. + pub fn into_table(self) -> Result<HashMap<String, Value>> { + match self.0.into_table() { + Ok(value) => Ok(value), + Err(error) => Err(error.extend_with_key(self.1)) + } } } |