♻️ Integra config-rs sólo para archivos TOML
This commit is contained in:
parent
4daab33499
commit
104efd116f
10 changed files with 2185 additions and 0 deletions
215
pagetop/src/config/data.rs
Normal file
215
pagetop/src/config/data.rs
Normal 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
462
pagetop/src/config/de.rs
Normal 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
222
pagetop/src/config/error.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
90
pagetop/src/config/file.rs
Normal file
90
pagetop/src/config/file.rs
Normal 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 })
|
||||
}
|
||||
}
|
||||
127
pagetop/src/config/file/source.rs
Normal file
127
pagetop/src/config/file/source.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
51
pagetop/src/config/file/toml.rs
Normal file
51
pagetop/src/config/file/toml.rs
Normal 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
254
pagetop/src/config/path.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
pagetop/src/config/path/parser.rs
Normal file
131
pagetop/src/config/path/parser.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
87
pagetop/src/config/source.rs
Normal file
87
pagetop/src/config/source.rs
Normal 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
546
pagetop/src/config/value.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue