summaryrefslogtreecommitdiffstats
path: root/src/value.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/value.rs')
-rw-r--r--src/value.rs571
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))
+ }
}
}