♻️ Integra config-rs sólo para archivos TOML

This commit is contained in:
Manuel Cillero 2022-10-21 00:59:17 +02:00
parent 4daab33499
commit 104efd116f
10 changed files with 2185 additions and 0 deletions

215
pagetop/src/config/data.rs Normal file
View file

@ -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<path::Expression, Value>,
overrides: HashMap<path::Expression, Value>,
sources: Vec<Box<dyn Source + Send + Sync>>,
},
}
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<T>(&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<T>(mut self, source: T) -> Result<Self>
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::<String, Value>::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<T>(&mut self, key: &str, value: T) -> Result<&mut ConfigData>
where
T: Into<Value>,
{
match self.kind {
ConfigKind::Mutable {
ref mut defaults, ..
} => {
defaults.insert(key.parse()?, value.into());
}
};
self.refresh()
}
pub fn set<T>(&mut self, key: &str, value: T) -> Result<&mut ConfigData>
where
T: Into<Value>,
{
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<T> {
// 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<String> {
self.get(key).and_then(Value::into_str)
}
pub fn get_int(&self, key: &str) -> Result<i64> {
self.get(key).and_then(Value::into_int)
}
pub fn get_float(&self, key: &str) -> Result<f64> {
self.get(key).and_then(Value::into_float)
}
pub fn get_bool(&self, key: &str) -> Result<bool> {
self.get(key).and_then(Value::into_bool)
}
pub fn get_table(&self, key: &str) -> Result<HashMap<String, Value>> {
self.get(key).and_then(Value::into_table)
}
pub fn get_array(&self, key: &str) -> Result<Vec<Value>> {
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> {
T::deserialize(self)
}
}
impl Source for ConfigData {
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
Box::new((*self).clone())
}
fn collect(&self) -> Result<HashMap<String, Value>> {
self.cache.clone().into_table()
}
}

462
pagetop/src/config/de.rs Normal file
View file

@ -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<V>(self, visitor: V) -> Result<V::Value>
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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_bool(self.into_bool()?)
}
#[inline]
fn deserialize_i8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_i64(self.into_int()?)
}
#[inline]
fn deserialize_u8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_f32(self.into_float()? as f32)
}
#[inline]
fn deserialize_f64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_f64(self.into_float()?)
}
#[inline]
fn deserialize_str<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_string(self.into_str()?)
}
#[inline]
fn deserialize_string<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_string(self.into_str()?)
}
#[inline]
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
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<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
fn deserialize_enum<V>(
self,
name: &'static str,
variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
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<Value>>,
}
impl SeqAccess {
fn new(elements: Vec<Value>) -> Self {
SeqAccess {
elements: elements.into_iter().enumerate(),
}
}
}
impl<'de> de::SeqAccess<'de> for SeqAccess {
type Error = ConfigError;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
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<usize> {
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<String, Value>) -> Self {
MapAccess {
elements: table.into_iter().collect(),
}
}
}
impl<'de> de::MapAccess<'de> for MapAccess {
type Error = ConfigError;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
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<V>(&mut self, seed: V) -> Result<V::Value>
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<StrDeserializer> {
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<StrDeserializer> {
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<V>(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<T>(self, seed: T) -> Result<T::Value>
where
T: de::DeserializeSeed<'de>,
{
match self.value.kind {
ValueKind::Table(t) => seed.deserialize(t.into_iter().next().unwrap().1),
_ => unreachable!(),
}
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
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<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
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<V>(self, visitor: V) -> Result<V::Value>
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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_bool(self.cache.into_bool()?)
}
#[inline]
fn deserialize_i8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_i64(self.cache.into_int()?)
}
#[inline]
fn deserialize_u8<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
// 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<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_f32(self.cache.into_float()? as f32)
}
#[inline]
fn deserialize_f64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_f64(self.cache.into_float()?)
}
#[inline]
fn deserialize_str<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_string(self.cache.into_str()?)
}
#[inline]
fn deserialize_string<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
visitor.visit_string(self.cache.into_str()?)
}
#[inline]
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
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<V>(
self,
name: &'static str,
variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
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
}
}

222
pagetop/src/config/error.rs Normal file
View file

@ -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<String>,
/// 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<dyn Error + Send + Sync>,
},
/// 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<String>,
/// 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<String>,
},
/// Custom message.
Message(String),
/// Unadorned error from a foreign origin.
Foreign(Box<dyn Error + Send + Sync>),
}
impl ConfigError {
// FIXME: pub(crate).
#[doc(hidden)]
pub fn invalid_type(
origin: Option<String>,
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<String>| {
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<T> = result::Result<T, ConfigError>;
// 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<T: fmt::Display>(msg: T) -> Self {
ConfigError::Message(msg.to_string())
}
}
impl ser::Error for ConfigError {
fn custom<T: fmt::Display>(msg: T) -> Self {
ConfigError::Message(msg.to_string())
}
}

View file

@ -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<T>
where
T: FileSource,
{
source: T,
/// A required File will error if it cannot be found.
required: bool,
}
impl File<source::FileSourceFile> {
/// 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<source::FileSourceFile> {
fn from(path: &'a Path) -> Self {
File {
required: true,
source: source::FileSourceFile::new(path.to_path_buf()),
}
}
}
impl From<PathBuf> for File<source::FileSourceFile> {
fn from(path: PathBuf) -> Self {
File {
required: true,
source: source::FileSourceFile::new(path),
}
}
}
impl<T: FileSource> File<T> {
pub fn required(mut self, required: bool) -> Self {
self.required = required;
self
}
}
impl<T: FileSource> Source for File<T>
where
T: 'static,
T: Sync + Send,
{
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
Box::new((*self).clone())
}
fn collect(&self) -> Result<HashMap<String, Value>> {
// 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 })
}
}

View file

@ -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>, String), Box<dyn Error + Send + Sync>>;
}
/// 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<PathBuf, Box<dyn Error + Send + Sync>> {
// 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>, String), Box<dyn Error + Send + Sync>> {
// 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<PathBuf> {
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<Component> = 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())
}
}

View file

@ -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<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
// 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()),
}
}

254
pagetop/src/config/path.rs Normal file
View file

@ -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<Expression>, String),
Subscript(Box<Expression>, isize),
}
impl FromStr for Expression {
type Err = ConfigError;
fn from_str(s: &str) -> Result<Expression> {
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::<String, Value>::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::<Value>::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::<String, Value>::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::<String, Value>::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::<String, Value>::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::<Value>::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;
}
}
}
}
}
}

View file

@ -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<Expression, ErrorKind> {
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<nom::error::Error<&str>>) -> 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);
}
}

View file

@ -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<dyn Source + Send + Sync>;
/// Collect all configuration properties available from this source and return a HashMap.
fn collect(&self) -> Result<HashMap<String, Value>>;
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<dyn Source + Send + Sync> {
fn clone(&self) -> Box<dyn Source + Send + Sync> {
self.clone_into_box()
}
}
impl Source for Vec<Box<dyn Source + Send + Sync>> {
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
Box::new((*self).clone())
}
fn collect(&self) -> Result<HashMap<String, Value>> {
let mut cache: Value = HashMap::<String, Value>::new().into();
for source in self {
source.collect_to(&mut cache)?;
}
if let ValueKind::Table(table) = cache.kind {
Ok(table)
} else {
unreachable!();
}
}
}
impl<T> Source for Vec<T>
where
T: Source + Sync + Send,
T: Clone,
T: 'static,
{
fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
Box::new((*self).clone())
}
fn collect(&self) -> Result<HashMap<String, Value>> {
let mut cache: Value = HashMap::<String, Value>::new().into();
for source in self {
source.collect_to(&mut cache)?;
}
if let ValueKind::Table(table) = cache.kind {
Ok(table)
} else {
unreachable!();
}
}
}

546
pagetop/src/config/value.rs Normal file
View file

@ -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<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)
}
}
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)
}
}
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<String>,
/// 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<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" => 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<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,
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<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,
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<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.
// 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",
)),
}
}
/// 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),
// 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<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))
}
#[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) = 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)) = visitor.next_entry()? {
values.insert(key, value);
}
Ok(values.into())
}
}
deserializer.deserialize_any(ValueVisitor)
}
}
impl<T> From<T> for Value
where
T: Into<ValueKind>,
{
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)
}
}