diff --git a/pagetop/src/config/data.rs b/pagetop/src/config/data.rs new file mode 100644 index 00000000..b9f72de7 --- /dev/null +++ b/pagetop/src/config/data.rs @@ -0,0 +1,215 @@ +use crate::config::error::*; +use crate::config::path; +use crate::config::source::Source; +use crate::config::value::{Table, Value}; + +use serde::de::Deserialize; + +use std::collections::HashMap; +use std::fmt::Debug; + +#[derive(Clone, Debug)] +enum ConfigKind { + // A mutable configuration. This is the default. + Mutable { + defaults: HashMap, + overrides: HashMap, + sources: Vec>, + }, +} + +impl Default for ConfigKind { + fn default() -> Self { + ConfigKind::Mutable { + defaults: HashMap::new(), + overrides: HashMap::new(), + sources: Vec::new(), + } + } +} + +/// 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, Clone, Debug)] +pub struct ConfigData { + kind: ConfigKind, + /// Root of the cached configuration. + pub cache: Value, +} + +impl ConfigData { + pub fn new() -> Self { + Self { + kind: ConfigKind::default(), + // Config root should be instantiated as an empty table to avoid deserialization errors. + cache: Value::new(None, Table::new()), + } + } + + /// Merge in a configuration property source. + pub fn merge(&mut self, source: T) -> Result<&mut ConfigData> + where + T: 'static, + T: Source + Send + Sync, + { + match self.kind { + ConfigKind::Mutable { + ref mut sources, .. + } => { + sources.push(Box::new(source)); + } + } + + self.refresh() + } + + /// Merge in a configuration property source. + pub fn with_merged(mut self, source: T) -> Result + where + T: 'static, + T: Source + Send + Sync, + { + match self.kind { + ConfigKind::Mutable { + ref mut sources, .. + } => { + sources.push(Box::new(source)); + } + } + + self.refresh()?; + Ok(self) + } + + /// Refresh the configuration cache with fresh data from added sources. + /// + /// Configuration is automatically refreshed after a mutation operation (`set`, `merge`, + /// `set_default`, etc.). + pub fn refresh(&mut self) -> Result<&mut ConfigData> { + self.cache = match self.kind { + // TODO: We need to actually merge in all the stuff. + ConfigKind::Mutable { + ref overrides, + ref sources, + ref defaults, + } => { + let mut cache: Value = HashMap::::new().into(); + + // Add defaults. + for (key, val) in defaults { + key.set(&mut cache, val.clone()); + } + + // Add sources. + sources.collect_to(&mut cache)?; + + // Add overrides. + for (key, val) in overrides { + key.set(&mut cache, val.clone()); + } + + cache + } + }; + + Ok(self) + } + + pub fn set_default(&mut self, key: &str, value: T) -> Result<&mut ConfigData> + where + T: Into, + { + match self.kind { + ConfigKind::Mutable { + ref mut defaults, .. + } => { + defaults.insert(key.parse()?, value.into()); + } + }; + + self.refresh() + } + + pub fn set(&mut self, key: &str, value: T) -> Result<&mut ConfigData> + where + T: Into, + { + match self.kind { + ConfigKind::Mutable { + ref mut overrides, .. + } => { + overrides.insert(key.parse()?, value.into()); + } + }; + + self.refresh() + } + + pub fn set_once(&mut self, key: &str, value: Value) -> Result<()> { + let expr: path::Expression = key.parse()?; + + // Traverse the cache using the path to (possibly) retrieve a value. + if let Some(ref mut val) = expr.get_mut(&mut self.cache) { + **val = value; + } else { + expr.set(&mut self.cache, value); + } + Ok(()) + } + + pub fn get<'de, T: Deserialize<'de>>(&self, key: &str) -> Result { + // Parse the key into a path expression. + let expr: path::Expression = key.parse()?; + + // Traverse the cache using the path to (possibly) retrieve a value. + let value = expr.get(&self.cache).cloned(); + + match value { + Some(value) => { + // Deserialize the received value into the requested type. + T::deserialize(value).map_err(|e| e.extend_with_key(key)) + } + + None => Err(ConfigError::NotFound(key.into())), + } + } + + pub fn get_str(&self, key: &str) -> Result { + self.get(key).and_then(Value::into_str) + } + + pub fn get_int(&self, key: &str) -> Result { + self.get(key).and_then(Value::into_int) + } + + pub fn get_float(&self, key: &str) -> Result { + self.get(key).and_then(Value::into_float) + } + + pub fn get_bool(&self, key: &str) -> Result { + self.get(key).and_then(Value::into_bool) + } + + pub fn get_table(&self, key: &str) -> Result> { + self.get(key).and_then(Value::into_table) + } + + pub fn get_array(&self, key: &str) -> Result> { + self.get(key).and_then(Value::into_array) + } + + /// Attempt to deserialize the entire configuration into the requested type. + pub fn try_into<'de, T: Deserialize<'de>>(self) -> Result { + T::deserialize(self) + } +} + +impl Source for ConfigData { + fn clone_into_box(&self) -> Box { + Box::new((*self).clone()) + } + + fn collect(&self) -> Result> { + self.cache.clone().into_table() + } +} diff --git a/pagetop/src/config/de.rs b/pagetop/src/config/de.rs new file mode 100644 index 00000000..1eb2ac6e --- /dev/null +++ b/pagetop/src/config/de.rs @@ -0,0 +1,462 @@ +use crate::config::data::ConfigData; +use crate::config::error::*; +use crate::config::value::{Table, Value, ValueKind}; + +use serde::de; +use serde::forward_to_deserialize_any; + +use std::collections::{HashMap, VecDeque}; +use std::iter::Enumerate; + +impl<'de> de::Deserializer<'de> for Value { + type Error = ConfigError; + + #[inline] + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + // Deserialize based on the underlying type. + match self.kind { + ValueKind::Nil => visitor.visit_unit(), + ValueKind::Integer(i) => visitor.visit_i64(i), + ValueKind::Boolean(b) => visitor.visit_bool(b), + ValueKind::Float(f) => visitor.visit_f64(f), + ValueKind::String(s) => visitor.visit_string(s), + ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)), + ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)), + } + } + + #[inline] + fn deserialize_bool>(self, visitor: V) -> Result { + visitor.visit_bool(self.into_bool()?) + } + + #[inline] + fn deserialize_i8>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_i8(self.into_int()? as i8) + } + + #[inline] + fn deserialize_i16>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_i16(self.into_int()? as i16) + } + + #[inline] + fn deserialize_i32>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_i32(self.into_int()? as i32) + } + + #[inline] + fn deserialize_i64>(self, visitor: V) -> Result { + visitor.visit_i64(self.into_int()?) + } + + #[inline] + fn deserialize_u8>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_u8(self.into_int()? as u8) + } + + #[inline] + fn deserialize_u16>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_u16(self.into_int()? as u16) + } + + #[inline] + fn deserialize_u32>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_u32(self.into_int()? as u32) + } + + #[inline] + fn deserialize_u64>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_u64(self.into_int()? as u64) + } + + #[inline] + fn deserialize_f32>(self, visitor: V) -> Result { + visitor.visit_f32(self.into_float()? as f32) + } + + #[inline] + fn deserialize_f64>(self, visitor: V) -> Result { + visitor.visit_f64(self.into_float()?) + } + + #[inline] + fn deserialize_str>(self, visitor: V) -> Result { + visitor.visit_string(self.into_str()?) + } + + #[inline] + fn deserialize_string>(self, visitor: V) -> Result { + visitor.visit_string(self.into_str()?) + } + + #[inline] + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + // Match an explicit nil as None and everything else as Some. + match self.kind { + ValueKind::Nil => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_enum(EnumAccess { + value: self, + name, + variants, + }) + } + + forward_to_deserialize_any! { + char seq + bytes byte_buf map struct unit + identifier ignored_any unit_struct tuple_struct tuple + } +} + +struct StrDeserializer<'a>(&'a str); + +impl<'de, 'a> de::Deserializer<'de> for StrDeserializer<'a> { + type Error = ConfigError; + + #[inline] + fn deserialize_any>(self, visitor: V) -> Result { + visitor.visit_str(self.0) + } + + forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map struct unit enum newtype_struct + identifier ignored_any unit_struct tuple_struct tuple option + } +} + +struct SeqAccess { + elements: Enumerate<::std::vec::IntoIter>, +} + +impl SeqAccess { + fn new(elements: Vec) -> Self { + SeqAccess { + elements: elements.into_iter().enumerate(), + } + } +} + +impl<'de> de::SeqAccess<'de> for SeqAccess { + type Error = ConfigError; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: de::DeserializeSeed<'de>, + { + match self.elements.next() { + Some((idx, value)) => seed + .deserialize(value) + .map(Some) + .map_err(|e| e.prepend_index(idx)), + None => Ok(None), + } + } + + fn size_hint(&self) -> Option { + match self.elements.size_hint() { + (lower, Some(upper)) if lower == upper => Some(upper), + _ => None, + } + } +} + +struct MapAccess { + elements: VecDeque<(String, Value)>, +} + +impl MapAccess { + fn new(table: HashMap) -> Self { + MapAccess { + elements: table.into_iter().collect(), + } + } +} + +impl<'de> de::MapAccess<'de> for MapAccess { + type Error = ConfigError; + + fn next_key_seed(&mut self, seed: K) -> Result> + where + K: de::DeserializeSeed<'de>, + { + if let Some(&(ref key_s, _)) = self.elements.front() { + let key_de = Value::new(None, key_s as &str); + let key = de::DeserializeSeed::deserialize(seed, key_de)?; + + Ok(Some(key)) + } else { + Ok(None) + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: de::DeserializeSeed<'de>, + { + let (key, value) = self.elements.pop_front().unwrap(); + de::DeserializeSeed::deserialize(seed, value).map_err(|e| e.prepend_key(key)) + } +} + +struct EnumAccess { + value: Value, + name: &'static str, + variants: &'static [&'static str], +} + +impl EnumAccess { + fn variant_deserializer(&self, name: &str) -> Result { + self.variants + .iter() + .find(|&&s| s == name) + .map(|&s| StrDeserializer(s)) + .ok_or_else(|| self.no_constructor_error(name)) + } + + fn table_deserializer(&self, table: &Table) -> Result { + if table.len() == 1 { + self.variant_deserializer(table.iter().next().unwrap().0) + } else { + Err(self.structural_error()) + } + } + + fn no_constructor_error(&self, supposed_variant: &str) -> ConfigError { + ConfigError::Message(format!( + "enum {} does not have variant constructor {}", + self.name, supposed_variant + )) + } + + fn structural_error(&self) -> ConfigError { + ConfigError::Message(format!( + "value of enum {} should be represented by either string or table with exactly one key", + self.name + )) + } +} + +impl<'de> de::EnumAccess<'de> for EnumAccess { + type Error = ConfigError; + type Variant = Self; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> + where + V: de::DeserializeSeed<'de>, + { + let value = { + let deserializer = match self.value.kind { + ValueKind::String(ref s) => self.variant_deserializer(s), + ValueKind::Table(ref t) => self.table_deserializer(&t), + _ => Err(self.structural_error()), + }?; + seed.deserialize(deserializer)? + }; + + Ok((value, self)) + } +} + +impl<'de> de::VariantAccess<'de> for EnumAccess { + type Error = ConfigError; + + fn unit_variant(self) -> Result<()> { + Ok(()) + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: de::DeserializeSeed<'de>, + { + match self.value.kind { + ValueKind::Table(t) => seed.deserialize(t.into_iter().next().unwrap().1), + _ => unreachable!(), + } + } + + fn tuple_variant(self, _len: usize, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.value.kind { + ValueKind::Table(t) => { + de::Deserializer::deserialize_seq(t.into_iter().next().unwrap().1, visitor) + } + _ => unreachable!(), + } + } + + fn struct_variant(self, _fields: &'static [&'static str], visitor: V) -> Result + where + V: de::Visitor<'de>, + { + match self.value.kind { + ValueKind::Table(t) => { + de::Deserializer::deserialize_map(t.into_iter().next().unwrap().1, visitor) + } + _ => unreachable!(), + } + } +} + +impl<'de> de::Deserializer<'de> for ConfigData { + type Error = ConfigError; + + #[inline] + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + // Deserialize based on the underlying type. + match self.cache.kind { + ValueKind::Nil => visitor.visit_unit(), + ValueKind::Integer(i) => visitor.visit_i64(i), + ValueKind::Boolean(b) => visitor.visit_bool(b), + ValueKind::Float(f) => visitor.visit_f64(f), + ValueKind::String(s) => visitor.visit_string(s), + ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)), + ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)), + } + } + + #[inline] + fn deserialize_bool>(self, visitor: V) -> Result { + visitor.visit_bool(self.cache.into_bool()?) + } + + #[inline] + fn deserialize_i8>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_i8(self.cache.into_int()? as i8) + } + + #[inline] + fn deserialize_i16>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_i16(self.cache.into_int()? as i16) + } + + #[inline] + fn deserialize_i32>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_i32(self.cache.into_int()? as i32) + } + + #[inline] + fn deserialize_i64>(self, visitor: V) -> Result { + visitor.visit_i64(self.cache.into_int()?) + } + + #[inline] + fn deserialize_u8>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_u8(self.cache.into_int()? as u8) + } + + #[inline] + fn deserialize_u16>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_u16(self.cache.into_int()? as u16) + } + + #[inline] + fn deserialize_u32>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_u32(self.cache.into_int()? as u32) + } + + #[inline] + fn deserialize_u64>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type. + visitor.visit_u64(self.cache.into_int()? as u64) + } + + #[inline] + fn deserialize_f32>(self, visitor: V) -> Result { + visitor.visit_f32(self.cache.into_float()? as f32) + } + + #[inline] + fn deserialize_f64>(self, visitor: V) -> Result { + visitor.visit_f64(self.cache.into_float()?) + } + + #[inline] + fn deserialize_str>(self, visitor: V) -> Result { + visitor.visit_string(self.cache.into_str()?) + } + + #[inline] + fn deserialize_string>(self, visitor: V) -> Result { + visitor.visit_string(self.cache.into_str()?) + } + + #[inline] + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + // Match an explicit nil as None and everything else as Some. + match self.cache.kind { + ValueKind::Nil => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + fn deserialize_enum( + self, + name: &'static str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: de::Visitor<'de>, + { + visitor.visit_enum(EnumAccess { + value: self.cache, + name, + variants, + }) + } + + forward_to_deserialize_any! { + char seq + bytes byte_buf map struct unit newtype_struct + identifier ignored_any unit_struct tuple_struct tuple + } +} diff --git a/pagetop/src/config/error.rs b/pagetop/src/config/error.rs new file mode 100644 index 00000000..815d71c8 --- /dev/null +++ b/pagetop/src/config/error.rs @@ -0,0 +1,222 @@ +use nom; +use serde::de; +use serde::ser; + +use std::error::Error; +use std::fmt; +use std::result; + +#[derive(Debug)] +pub enum Unexpected { + Bool(bool), + Integer(i64), + Float(f64), + Str(String), + Unit, + Seq, + Map, +} + +impl fmt::Display for Unexpected { + fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { + match *self { + Unexpected::Bool(b) => write!(f, "boolean `{}`", b), + Unexpected::Integer(i) => write!(f, "integer `{}`", i), + Unexpected::Float(v) => write!(f, "floating point `{}`", v), + Unexpected::Str(ref s) => write!(f, "string {:?}", s), + Unexpected::Unit => write!(f, "unit value"), + Unexpected::Seq => write!(f, "sequence"), + Unexpected::Map => write!(f, "map"), + } + } +} + +/// Represents all possible errors that can occur when working with configuration. +pub enum ConfigError { + /// Configuration is frozen and no further mutations can be made. + Frozen, + + /// Configuration property was not found. + NotFound(String), + + /// Configuration path could not be parsed. + PathParse(nom::error::ErrorKind), + + /// Configuration could not be parsed from file. + FileParse { + /// The URI used to access the file (if not loaded from a string). + /// Example: `/path/to/config.json` + uri: Option, + + /// The captured error from attempting to parse the file in its desired format. + /// This is the actual error object from the library used for the parsing. + cause: Box, + }, + + /// Value could not be converted into the requested type. + Type { + /// The URI that references the source that the value came from. + /// Example: `/path/to/config.json` or `Environment` or `etcd://localhost` + // TODO: Why is this called Origin but FileParse has a uri field? + origin: Option, + + /// What we found when parsing the value. + unexpected: Unexpected, + + /// What was expected when parsing the value. + expected: &'static str, + + /// The key in the configuration hash of this value (if available where the error is + /// generated). + key: Option, + }, + + /// Custom message. + Message(String), + + /// Unadorned error from a foreign origin. + Foreign(Box), +} + +impl ConfigError { + // FIXME: pub(crate). + #[doc(hidden)] + pub fn invalid_type( + origin: Option, + unexpected: Unexpected, + expected: &'static str, + ) -> Self { + ConfigError::Type { + origin, + unexpected, + expected, + key: None, + } + } + + // FIXME: pub(crate). + #[doc(hidden)] + pub fn extend_with_key(self, key: &str) -> Self { + match self { + ConfigError::Type { + origin, + unexpected, + expected, + .. + } => ConfigError::Type { + origin, + unexpected, + expected, + key: Some(key.into()), + }, + + _ => self, + } + } + + fn prepend(self, segment: String, add_dot: bool) -> Self { + let concat = |key: Option| { + let key = key.unwrap_or_else(String::new); + let dot = if add_dot && key.as_bytes().get(0).unwrap_or(&b'[') != &b'[' { + "." + } else { + "" + }; + format!("{}{}{}", segment, dot, key) + }; + match self { + ConfigError::Type { + origin, + unexpected, + expected, + key, + } => ConfigError::Type { + origin, + unexpected, + expected, + key: Some(concat(key)), + }, + ConfigError::NotFound(key) => ConfigError::NotFound(concat(Some(key))), + _ => self, + } + } + + pub(crate) fn prepend_key(self, key: String) -> Self { + self.prepend(key, true) + } + + pub(crate) fn prepend_index(self, idx: usize) -> Self { + self.prepend(format!("[{}]", idx), false) + } +} + +/// Alias for a `Result` with the error type set to `ConfigError`. +pub type Result = result::Result; + +// Forward Debug to Display for readable panic! messages. +impl fmt::Debug for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", *self) + } +} + +impl fmt::Display for ConfigError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ConfigError::Frozen => write!(f, "configuration is frozen"), + + ConfigError::PathParse(ref kind) => write!(f, "{}", kind.description()), + + ConfigError::Message(ref s) => write!(f, "{}", s), + + ConfigError::Foreign(ref cause) => write!(f, "{}", cause), + + ConfigError::NotFound(ref key) => { + write!(f, "configuration property {:?} not found", key) + } + + ConfigError::Type { + ref origin, + ref unexpected, + expected, + ref key, + } => { + write!(f, "invalid type: {}, expected {}", unexpected, expected)?; + + if let Some(ref key) = *key { + write!(f, " for key `{}`", key)?; + } + + if let Some(ref origin) = *origin { + write!(f, " in {}", origin)?; + } + + Ok(()) + } + + ConfigError::FileParse { ref cause, ref uri } => { + write!(f, "{}", cause)?; + + if let Some(ref uri) = *uri { + write!(f, " in {}", uri)?; + } + + Ok(()) + } + } + } +} + +impl Error for ConfigError {} + +impl de::Error for ConfigError { + fn custom(msg: T) -> Self { + ConfigError::Message(msg.to_string()) + } +} + +impl ser::Error for ConfigError { + fn custom(msg: T) -> Self { + ConfigError::Message(msg.to_string()) + } +} diff --git a/pagetop/src/config/file.rs b/pagetop/src/config/file.rs new file mode 100644 index 00000000..8f05bffb --- /dev/null +++ b/pagetop/src/config/file.rs @@ -0,0 +1,90 @@ +mod source; +mod toml; + +use crate::config::error::*; +use crate::config::source::Source; +use crate::config::value::Value; + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +use self::source::FileSource; + +#[derive(Clone, Debug)] +pub struct File +where + T: FileSource, +{ + source: T, + + /// A required File will error if it cannot be found. + required: bool, +} + +impl File { + /// Given the basename of a file, will attempt to locate a file by setting its extension to a + /// registered format. + pub fn with_name(name: &str) -> Self { + File { + required: true, + source: source::FileSourceFile::new(name.into()), + } + } +} + +impl<'a> From<&'a Path> for File { + fn from(path: &'a Path) -> Self { + File { + required: true, + source: source::FileSourceFile::new(path.to_path_buf()), + } + } +} + +impl From for File { + fn from(path: PathBuf) -> Self { + File { + required: true, + source: source::FileSourceFile::new(path), + } + } +} + +impl File { + pub fn required(mut self, required: bool) -> Self { + self.required = required; + self + } +} + +impl Source for File +where + T: 'static, + T: Sync + Send, +{ + fn clone_into_box(&self) -> Box { + Box::new((*self).clone()) + } + + fn collect(&self) -> Result> { + // Coerce the file contents to a string. + let (uri, contents) = match self + .source + .resolve() + .map_err(|err| ConfigError::Foreign(err)) + { + Ok((uri, contents)) => (uri, contents), + + Err(error) => { + if !self.required { + return Ok(HashMap::new()); + } + + return Err(error); + } + }; + + // Parse the string using the given format. + toml::parse(uri.as_ref(), &contents).map_err(|cause| ConfigError::FileParse { uri, cause }) + } +} diff --git a/pagetop/src/config/file/source.rs b/pagetop/src/config/file/source.rs new file mode 100644 index 00000000..e793f4ec --- /dev/null +++ b/pagetop/src/config/file/source.rs @@ -0,0 +1,127 @@ +use std::error::Error; +use std::env; +use std::fmt::Debug; +use std::fs; +use std::io::{self, Read}; +use std::iter::Iterator; +use std::path::{Path, PathBuf}; + + +/// Describes where the file is sourced. +pub trait FileSource: Debug + Clone { + fn resolve(&self) -> Result<(Option, String), Box>; +} + +/// Describes a file sourced from a file. +#[derive(Clone, Debug)] +pub struct FileSourceFile { + /// Path of configuration file. + name: PathBuf, +} + +impl FileSourceFile { + pub fn new(name: PathBuf) -> FileSourceFile { + FileSourceFile { name } + } + + fn find_file(&self) -> Result> { + // First check for an _exact_ match. + let mut filename = env::current_dir()?.as_path().join(self.name.clone()); + if filename.is_file() { + if vec!["toml"].contains( + &filename + .extension() + .unwrap_or_default() + .to_string_lossy() + .as_ref(), + ) { + return Ok(filename); + } + + Err(Box::new(io::Error::new( + io::ErrorKind::NotFound, + format!( + "configuration file \"{}\" is not of a registered file format", + filename.to_string_lossy() + ), + ))) + } else { + filename.set_extension("toml"); + + if filename.is_file() { + return Ok(filename); + } + + Err(Box::new(io::Error::new( + io::ErrorKind::NotFound, + format!( + "configuration file \"{}\" not found", + self.name.to_string_lossy() + ), + ))) + } + } +} + +impl FileSource for FileSourceFile { + fn resolve(&self) -> Result<(Option, String), Box> { + // Find file. + let filename = self.find_file()?; + + // Attempt to use a relative path for the URI. + let base = env::current_dir()?; + let uri = match path_relative_from(&filename, &base) { + Some(value) => value, + None => filename.clone(), + }; + + // Read contents from file. + let mut file = fs::File::open(filename)?; + let mut text = String::new(); + file.read_to_string(&mut text)?; + + Ok((Some(uri.to_string_lossy().into_owned()), text)) + } +} + +// TODO: This should probably be a crate. +// https://github.com/rust-lang/rust/blob/master/src/librustc_trans/back/rpath.rs#L128 +fn path_relative_from(path: &Path, base: &Path) -> Option { + use std::path::Component; + + if path.is_absolute() != base.is_absolute() { + if path.is_absolute() { + Some(PathBuf::from(path)) + } else { + None + } + } else { + let mut ita = path.components(); + let mut itb = base.components(); + let mut comps: Vec = vec![]; + loop { + match (ita.next(), itb.next()) { + (None, None) => break, + (Some(a), None) => { + comps.push(a); + comps.extend(ita.by_ref()); + break; + } + (None, _) => comps.push(Component::ParentDir), + (Some(a), Some(b)) if comps.is_empty() && a == b => (), + (Some(a), Some(b)) if b == Component::CurDir => comps.push(a), + (Some(_), Some(b)) if b == Component::ParentDir => return None, + (Some(a), Some(_)) => { + comps.push(Component::ParentDir); + for _ in itb { + comps.push(Component::ParentDir); + } + comps.push(a); + comps.extend(ita.by_ref()); + break; + } + } + } + Some(comps.iter().map(|c| c.as_os_str()).collect()) + } +} diff --git a/pagetop/src/config/file/toml.rs b/pagetop/src/config/file/toml.rs new file mode 100644 index 00000000..e8fa06c6 --- /dev/null +++ b/pagetop/src/config/file/toml.rs @@ -0,0 +1,51 @@ +use crate::config::value::{Value, ValueKind}; + +use toml; + +use std::collections::HashMap; +use std::error::Error; + +pub fn parse( + uri: Option<&String>, + text: &str, +) -> Result, Box> { + // Parse a TOML value from the provided text. + // TODO: Have a proper error fire if the root of a file is ever not a Table + let value = from_toml_value(uri, &toml::from_str(text)?); + match value.kind { + ValueKind::Table(map) => Ok(map), + + _ => Ok(HashMap::new()), + } +} + +fn from_toml_value(uri: Option<&String>, value: &toml::Value) -> Value { + match *value { + toml::Value::String(ref value) => Value::new(uri, value.to_string()), + toml::Value::Float(value) => Value::new(uri, value), + toml::Value::Integer(value) => Value::new(uri, value), + toml::Value::Boolean(value) => Value::new(uri, value), + + toml::Value::Table(ref table) => { + let mut m = HashMap::new(); + + for (key, value) in table { + m.insert(key.clone(), from_toml_value(uri, value)); + } + + Value::new(uri, m) + } + + toml::Value::Array(ref array) => { + let mut l = Vec::new(); + + for value in array { + l.push(from_toml_value(uri, value)); + } + + Value::new(uri, l) + } + + toml::Value::Datetime(ref datetime) => Value::new(uri, datetime.to_string()), + } +} diff --git a/pagetop/src/config/path.rs b/pagetop/src/config/path.rs new file mode 100644 index 00000000..1afe2ddd --- /dev/null +++ b/pagetop/src/config/path.rs @@ -0,0 +1,254 @@ +use crate::config::error::*; +use crate::config::value::{Value, ValueKind}; + +use std::collections::HashMap; +use std::str::FromStr; + +mod parser; + +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub enum Expression { + Identifier(String), + Child(Box, String), + Subscript(Box, isize), +} + +impl FromStr for Expression { + type Err = ConfigError; + + fn from_str(s: &str) -> Result { + parser::from_str(s).map_err(ConfigError::PathParse) + } +} + +fn sindex_to_uindex(index: isize, len: usize) -> usize { + if index >= 0 { + index as usize + } else { + len - (index.abs() as usize) + } +} + +impl Expression { + pub fn get(self, root: &Value) -> Option<&Value> { + match self { + Expression::Identifier(id) => { + match root.kind { + // `x` access on a table is equivalent to: map[x]. + ValueKind::Table(ref map) => map.get(&id), + + // All other variants return None. + _ => None, + } + } + + Expression::Child(expr, key) => { + match expr.get(root) { + Some(value) => { + match value.kind { + // Access on a table is identical to Identifier, it just forwards. + ValueKind::Table(ref map) => map.get(&key), + + // All other variants return None. + _ => None, + } + } + + _ => None, + } + } + + Expression::Subscript(expr, index) => match expr.get(root) { + Some(value) => match value.kind { + ValueKind::Array(ref array) => { + let index = sindex_to_uindex(index, array.len()); + + if index >= array.len() { + None + } else { + Some(&array[index]) + } + } + + _ => None, + }, + + _ => None, + }, + } + } + + pub fn get_mut<'a>(&self, root: &'a mut Value) -> Option<&'a mut Value> { + match *self { + Expression::Identifier(ref id) => match root.kind { + ValueKind::Table(ref mut map) => map.get_mut(id), + + _ => None, + }, + + Expression::Child(ref expr, ref key) => match expr.get_mut(root) { + Some(value) => match value.kind { + ValueKind::Table(ref mut map) => map.get_mut(key), + + _ => None, + }, + + _ => None, + }, + + Expression::Subscript(ref expr, index) => match expr.get_mut(root) { + Some(value) => match value.kind { + ValueKind::Array(ref mut array) => { + let index = sindex_to_uindex(index, array.len()); + + if index >= array.len() { + None + } else { + Some(&mut array[index]) + } + } + + _ => None, + }, + + _ => None, + }, + } + } + + pub fn get_mut_forcibly<'a>(&self, root: &'a mut Value) -> Option<&'a mut Value> { + match *self { + Expression::Identifier(ref id) => match root.kind { + ValueKind::Table(ref mut map) => Some( + map.entry(id.clone()) + .or_insert_with(|| Value::new(None, ValueKind::Nil)), + ), + + _ => None, + }, + + Expression::Child(ref expr, ref key) => match expr.get_mut_forcibly(root) { + Some(value) => match value.kind { + ValueKind::Table(ref mut map) => Some( + map.entry(key.clone()) + .or_insert_with(|| Value::new(None, ValueKind::Nil)), + ), + + _ => { + *value = HashMap::::new().into(); + + if let ValueKind::Table(ref mut map) = value.kind { + Some( + map.entry(key.clone()) + .or_insert_with(|| Value::new(None, ValueKind::Nil)), + ) + } else { + unreachable!(); + } + } + }, + + _ => None, + }, + + Expression::Subscript(ref expr, index) => match expr.get_mut_forcibly(root) { + Some(value) => { + match value.kind { + ValueKind::Array(_) => (), + _ => *value = Vec::::new().into(), + } + + match value.kind { + ValueKind::Array(ref mut array) => { + let index = sindex_to_uindex(index, array.len()); + + if index >= array.len() { + array + .resize((index + 1) as usize, Value::new(None, ValueKind::Nil)); + } + + Some(&mut array[index]) + } + + _ => None, + } + } + _ => None, + }, + } + } + + pub fn set(&self, root: &mut Value, value: Value) { + match *self { + Expression::Identifier(ref id) => { + // Ensure that root is a table. + match root.kind { + ValueKind::Table(_) => {} + + _ => { + *root = HashMap::::new().into(); + } + } + + match value.kind { + ValueKind::Table(ref incoming_map) => { + // Pull out another table. + let mut target = if let ValueKind::Table(ref mut map) = root.kind { + map.entry(id.clone()) + .or_insert_with(|| HashMap::::new().into()) + } else { + unreachable!(); + }; + + // Continue the deep merge. + for (key, val) in incoming_map { + Expression::Identifier(key.clone()).set(&mut target, val.clone()); + } + } + + _ => { + if let ValueKind::Table(ref mut map) = root.kind { + // Just do a simple set. + map.insert(id.clone(), value); + } + } + } + } + + Expression::Child(ref expr, ref key) => { + if let Some(parent) = expr.get_mut_forcibly(root) { + match parent.kind { + ValueKind::Table(_) => { + Expression::Identifier(key.clone()).set(parent, value); + } + + _ => { + // Didn't find a table. Oh well. Make a table and do this anyway. + *parent = HashMap::::new().into(); + + Expression::Identifier(key.clone()).set(parent, value); + } + } + } + } + + Expression::Subscript(ref expr, index) => { + if let Some(parent) = expr.get_mut_forcibly(root) { + match parent.kind { + ValueKind::Array(_) => (), + _ => *parent = Vec::::new().into(), + } + + if let ValueKind::Array(ref mut array) = parent.kind { + let uindex = sindex_to_uindex(index, array.len()); + if uindex >= array.len() { + array.resize((uindex + 1) as usize, Value::new(None, ValueKind::Nil)); + } + + array[uindex] = value; + } + } + } + } + } +} diff --git a/pagetop/src/config/path/parser.rs b/pagetop/src/config/path/parser.rs new file mode 100644 index 00000000..6bc95276 --- /dev/null +++ b/pagetop/src/config/path/parser.rs @@ -0,0 +1,131 @@ +use super::Expression; + +use nom::{ + branch::alt, + bytes::complete::{is_a, tag}, + character::complete::{char, digit1, space0}, + combinator::{map, map_res, opt, recognize}, + error::ErrorKind, + sequence::{delimited, pair, preceded}, + Err, IResult, +}; + +use std::str::FromStr; + +fn raw_ident(i: &str) -> IResult<&str, String> { + map( + is_a( + "abcdefghijklmnopqrstuvwxyz \ + ABCDEFGHIJKLMNOPQRSTUVWXYZ \ + 0123456789 \ + _-", + ), + |s: &str| s.to_string(), + )(i) +} + +fn integer(i: &str) -> IResult<&str, isize> { + map_res( + delimited(space0, recognize(pair(opt(tag("-")), digit1)), space0), + FromStr::from_str, + )(i) +} + +fn ident(i: &str) -> IResult<&str, Expression> { + map(raw_ident, Expression::Identifier)(i) +} + +fn postfix<'a>(expr: Expression) -> impl FnMut(&'a str) -> IResult<&'a str, Expression> { + let e2 = expr.clone(); + let child = map(preceded(tag("."), raw_ident), move |id| { + Expression::Child(Box::new(expr.clone()), id) + }); + + let subscript = map(delimited(char('['), integer, char(']')), move |num| { + Expression::Subscript(Box::new(e2.clone()), num) + }); + + alt((child, subscript)) +} + +pub fn from_str(input: &str) -> Result { + match ident(input) { + Ok((mut rem, mut expr)) => { + while !rem.is_empty() { + match postfix(expr)(rem) { + Ok((rem_, expr_)) => { + rem = rem_; + expr = expr_; + } + + // Forward Incomplete and Error + result => { + return result.map(|(_, o)| o).map_err(to_error_kind); + } + } + } + + Ok(expr) + } + + // Forward Incomplete and Error + result => result.map(|(_, o)| o).map_err(to_error_kind), + } +} + +pub fn to_error_kind(e: Err>) -> ErrorKind { + match e { + Err::Incomplete(_) => ErrorKind::Complete, + Err::Failure(e) | Err::Error(e) => e.code, + } +} + +#[cfg(test)] +mod test { + use super::Expression::*; + use super::*; + + #[test] + fn test_id() { + let parsed: Expression = from_str("abcd").unwrap(); + assert_eq!(parsed, Identifier("abcd".into())); + } + + #[test] + fn test_id_dash() { + let parsed: Expression = from_str("abcd-efgh").unwrap(); + assert_eq!(parsed, Identifier("abcd-efgh".into())); + } + + #[test] + fn test_child() { + let parsed: Expression = from_str("abcd.efgh").unwrap(); + let expected = Child(Box::new(Identifier("abcd".into())), "efgh".into()); + + assert_eq!(parsed, expected); + + let parsed: Expression = from_str("abcd.efgh.ijkl").unwrap(); + let expected = Child( + Box::new(Child(Box::new(Identifier("abcd".into())), "efgh".into())), + "ijkl".into(), + ); + + assert_eq!(parsed, expected); + } + + #[test] + fn test_subscript() { + let parsed: Expression = from_str("abcd[12]").unwrap(); + let expected = Subscript(Box::new(Identifier("abcd".into())), 12); + + assert_eq!(parsed, expected); + } + + #[test] + fn test_subscript_neg() { + let parsed: Expression = from_str("abcd[-1]").unwrap(); + let expected = Subscript(Box::new(Identifier("abcd".into())), -1); + + assert_eq!(parsed, expected); + } +} diff --git a/pagetop/src/config/source.rs b/pagetop/src/config/source.rs new file mode 100644 index 00000000..5e693b68 --- /dev/null +++ b/pagetop/src/config/source.rs @@ -0,0 +1,87 @@ +use crate::config::error::*; +use crate::config::path; +use crate::config::value::{Value, ValueKind}; + +use std::collections::HashMap; +use std::fmt::Debug; +use std::str::FromStr; + +/// Describes a generic _source_ of configuration properties. +pub trait Source: Debug { + fn clone_into_box(&self) -> Box; + + /// Collect all configuration properties available from this source and return a HashMap. + fn collect(&self) -> Result>; + + fn collect_to(&self, cache: &mut Value) -> Result<()> { + let props = match self.collect() { + Ok(props) => props, + Err(error) => { + return Err(error); + } + }; + + for (key, val) in &props { + match path::Expression::from_str(key) { + // Set using the path. + Ok(expr) => expr.set(cache, val.clone()), + + // Set diretly anyway. + _ => path::Expression::Identifier(key.clone()).set(cache, val.clone()), + } + } + + Ok(()) + } +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.clone_into_box() + } +} + +impl Source for Vec> { + fn clone_into_box(&self) -> Box { + Box::new((*self).clone()) + } + + fn collect(&self) -> Result> { + let mut cache: Value = HashMap::::new().into(); + + for source in self { + source.collect_to(&mut cache)?; + } + + if let ValueKind::Table(table) = cache.kind { + Ok(table) + } else { + unreachable!(); + } + } +} + +impl Source for Vec +where + T: Source + Sync + Send, + T: Clone, + T: 'static, +{ + fn clone_into_box(&self) -> Box { + Box::new((*self).clone()) + } + + fn collect(&self) -> Result> { + let mut cache: Value = HashMap::::new().into(); + + for source in self { + source.collect_to(&mut cache)?; + } + + if let ValueKind::Table(table) = cache.kind { + Ok(table) + } else { + unreachable!(); + } + } +} diff --git a/pagetop/src/config/value.rs b/pagetop/src/config/value.rs new file mode 100644 index 00000000..1e1e6e67 --- /dev/null +++ b/pagetop/src/config/value.rs @@ -0,0 +1,546 @@ +use crate::config::error::*; + +use serde::de::{Deserialize, Deserializer, Visitor}; + +use std::collections::HashMap; +use std::fmt; +use std::fmt::Display; + +/// Underlying kind of the configuration value. +#[derive(Debug, Clone, PartialEq)] +pub enum ValueKind { + Nil, + Boolean(bool), + Integer(i64), + Float(f64), + String(String), + Table(Table), + Array(Array), +} + +pub type Array = Vec; +pub type Table = HashMap; + +impl Default for ValueKind { + fn default() -> Self { + ValueKind::Nil + } +} + +impl From> for ValueKind +where + T: Into, +{ + fn from(value: Option) -> Self { + match value { + Some(value) => value.into(), + None => ValueKind::Nil, + } + } +} + +impl From for ValueKind { + fn from(value: String) -> Self { + ValueKind::String(value) + } +} + +impl<'a> From<&'a str> for ValueKind { + fn from(value: &'a str) -> Self { + ValueKind::String(value.into()) + } +} + +impl From for ValueKind { + fn from(value: i64) -> Self { + ValueKind::Integer(value) + } +} + +impl From for ValueKind { + fn from(value: f64) -> Self { + ValueKind::Float(value) + } +} + +impl From for ValueKind { + fn from(value: bool) -> Self { + ValueKind::Boolean(value) + } +} + +impl From> for ValueKind +where + T: Into, +{ + fn from(values: HashMap) -> Self { + let mut r = HashMap::new(); + + for (k, v) in values { + r.insert(k.clone(), v.into()); + } + + ValueKind::Table(r) + } +} + +impl From> for ValueKind +where + T: Into, +{ + fn from(values: Vec) -> Self { + let mut l = Vec::new(); + + for v in values { + l.push(v.into()); + } + + ValueKind::Array(l) + } +} + +impl Display for ValueKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ValueKind::String(ref value) => write!(f, "{}", value), + ValueKind::Boolean(value) => write!(f, "{}", value), + ValueKind::Integer(value) => write!(f, "{}", value), + ValueKind::Float(value) => write!(f, "{}", value), + ValueKind::Nil => write!(f, "nil"), + + // TODO: Figure out a nice Display for these + ValueKind::Table(ref table) => write!(f, "{:?}", table), + ValueKind::Array(ref array) => write!(f, "{:?}", array), + } + } +} + +/// A configuration value. +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Value { + /// A description of the original location of the value. + /// + /// A Value originating from a File might contain: + /// ```text + /// Settings.toml + /// ``` + /// + /// A Value originating from the environment would contain: + /// ```text + /// the envrionment + /// ``` + /// + /// A Value originating from a remote source might contain: + /// ```text + /// etcd+http://127.0.0.1:2379 + /// ``` + origin: Option, + + /// Underlying kind of the configuration value. + pub kind: ValueKind, +} + +impl Value { + /// Create a new value instance that will remember its source uri. + pub fn new(origin: Option<&String>, kind: V) -> Self + where + V: Into, + { + 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::deserialize(self) + } + + /// Returns `self` as a bool, if possible. + // FIXME: Should this not be `try_into_*` ? + pub fn into_bool(self) -> Result { + 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, + Unexpected::Unit, + "a boolean", + )), + ValueKind::Table(_) => Err(ConfigError::invalid_type( + self.origin, + Unexpected::Map, + "a boolean", + )), + ValueKind::Array(_) => Err(ConfigError::invalid_type( + self.origin, + Unexpected::Seq, + "a boolean", + )), + } + } + + /// Returns `self` into an i64, if possible. + // FIXME: Should this not be `try_into_*` ? + pub fn into_int(self) -> Result { + 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, + Unexpected::Unit, + "an integer", + )), + ValueKind::Table(_) => Err(ConfigError::invalid_type( + self.origin, + Unexpected::Map, + "an integer", + )), + ValueKind::Array(_) => Err(ConfigError::invalid_type( + self.origin, + Unexpected::Seq, + "an integer", + )), + } + } + + /// Returns `self` into a f64, if possible. + // FIXME: Should this not be `try_into_*` ? + pub fn into_float(self) -> Result { + 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, + Unexpected::Unit, + "a floating point", + )), + ValueKind::Table(_) => Err(ConfigError::invalid_type( + self.origin, + Unexpected::Map, + "a floating point", + )), + ValueKind::Array(_) => Err(ConfigError::invalid_type( + self.origin, + Unexpected::Seq, + "a floating point", + )), + } + } + + /// Returns `self` into a str, if possible. + // FIXME: Should this not be `try_into_*` ? + pub fn into_str(self) -> Result { + 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. + // FIXME: Should this not be `try_into_*` ? + pub fn into_array(self) -> Result> { + 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. + // FIXME: Should this not be `try_into_*` ? + pub fn into_table(self) -> Result> { + 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<'de> Deserialize<'de> for Value { + #[inline] + fn deserialize(deserializer: D) -> ::std::result::Result + 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(self, value: bool) -> ::std::result::Result { + Ok(value.into()) + } + + #[inline] + fn visit_i8(self, value: i8) -> ::std::result::Result { + Ok((value as i64).into()) + } + + #[inline] + fn visit_i16(self, value: i16) -> ::std::result::Result { + Ok((value as i64).into()) + } + + #[inline] + fn visit_i32(self, value: i32) -> ::std::result::Result { + Ok((value as i64).into()) + } + + #[inline] + fn visit_i64(self, value: i64) -> ::std::result::Result { + Ok(value.into()) + } + + #[inline] + fn visit_u8(self, value: u8) -> ::std::result::Result { + Ok((value as i64).into()) + } + + #[inline] + fn visit_u16(self, value: u16) -> ::std::result::Result { + Ok((value as i64).into()) + } + + #[inline] + fn visit_u32(self, value: u32) -> ::std::result::Result { + Ok((value as i64).into()) + } + + #[inline] + fn visit_u64(self, value: u64) -> ::std::result::Result { + // FIXME: This is bad + Ok((value as i64).into()) + } + + #[inline] + fn visit_f64(self, value: f64) -> ::std::result::Result { + Ok(value.into()) + } + + #[inline] + fn visit_str(self, value: &str) -> ::std::result::Result + where + E: ::serde::de::Error, + { + self.visit_string(String::from(value)) + } + + #[inline] + fn visit_string(self, value: String) -> ::std::result::Result { + Ok(value.into()) + } + + #[inline] + fn visit_none(self) -> ::std::result::Result { + Ok(Value::new(None, ValueKind::Nil)) + } + + #[inline] + fn visit_some(self, deserializer: D) -> ::std::result::Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer) + } + + #[inline] + fn visit_unit(self) -> ::std::result::Result { + Ok(Value::new(None, ValueKind::Nil)) + } + + #[inline] + fn visit_seq(self, mut visitor: V) -> ::std::result::Result + where + V: ::serde::de::SeqAccess<'de>, + { + let mut vec = Array::new(); + + while let Some(elem) = visitor.next_element()? { + vec.push(elem); + } + + Ok(vec.into()) + } + + fn visit_map(self, mut visitor: V) -> ::std::result::Result + where + V: ::serde::de::MapAccess<'de>, + { + let mut values = Table::new(); + + while let Some((key, value)) = visitor.next_entry()? { + values.insert(key, value); + } + + Ok(values.into()) + } + } + + deserializer.deserialize_any(ValueVisitor) + } +} + +impl From for Value +where + T: Into, +{ + fn from(value: T) -> Self { + Value { + origin: None, + kind: value.into(), + } + } +} + +impl Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.kind) + } +}