From cb4888dbbd3f2ebd1bd4e35fb07fb2fe0facafe8 Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Sun, 30 Jul 2017 14:22:50 -0700 Subject: Impl Deserializer for Config (to forward Value) --- CHANGELOG.md | 33 +++++++++ Cargo.toml | 2 +- examples/hierarchical-env/src/settings.rs | 2 +- src/config.rs | 12 ++-- src/de.rs | 112 ++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b7b47b..ca03533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 0.7.0 + - Fix conflict with `serde_yaml`. [#39] + +[#39]: https://github.com/mehcode/config-rs/issues/39 + + - Implement `Source` for `Config`. + - Implement `serde::de::Deserializer` for `Config`. `my_config.deserialize` may now be called as either `Deserialize::deserialize(my_config)` or `my_config.try_into()`. + - Remove `ConfigResult`. The builder pattern requires either `.try_into` as the final step _or_ the initial `Config::new()` to be bound to a slot. Errors must also be handled on each call instead of at the end of the chain. + + + ```rust + let mut c = Config::new(); + c + .merge(File::with_name("Settings")).unwrap() + .merge(Environment::with_prefix("APP")).unwrap(); + ``` + + ```rust + let c = Config::new() + .merge(File::with_name("Settings")).unwrap() + .merge(Environment::with_prefix("APP")).unwrap() + // LLVM should be smart enough to remove the actual clone operation + // as you are cloning a temporary that is dropped at the same time + .clone(); + ``` + + ```rust + let mut s: Settings = Config::new() + .merge(File::with_name("Settings")).unwrap() + .merge(Environment::with_prefix("APP")).unwrap() + .try_into(); + ``` + ## 0.6.0 – 2017-06-22 - Implement `Source` for `Vec` and `Vec>` diff --git a/Cargo.toml b/Cargo.toml index 3a61e3d..1317a27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "config" -version = "0.6.1-pre" +version = "0.7.0" description = "Layered configuration system for Rust applications." homepage = "https://github.com/mehcode/config-rs" repository = "https://github.com/mehcode/config-rs" diff --git a/examples/hierarchical-env/src/settings.rs b/examples/hierarchical-env/src/settings.rs index 9e07054..c908f46 100644 --- a/examples/hierarchical-env/src/settings.rs +++ b/examples/hierarchical-env/src/settings.rs @@ -65,6 +65,6 @@ impl Settings { println!("database: {:?}", s.get::("database.url")); // You can deserialize (and thus freeze) the entire configuration as - s.deserialize() + s.try_into() } } diff --git a/src/config.rs b/src/config.rs index f890461..fac76a5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::ops::Deref; use std::str::FromStr; use std::fmt::Debug; -use serde::de::Deserialize; +use serde::de::{Deserialize, Deserializer}; use error::*; use source::Source; @@ -110,11 +110,6 @@ impl Config { Ok(self) } - /// Deserialize the entire configuration. - pub fn deserialize<'de, T: Deserialize<'de>>(&self) -> Result { - T::deserialize(self.cache.clone()) - } - pub fn set_default(&mut self, key: &str, value: T) -> Result<&mut Config> where T: Into, @@ -189,6 +184,11 @@ impl Config { pub fn get_array(&self, key: &str) -> Result> { self.get(key).and_then(Value::into_array) } + + /// Attempt to deserialize the entire configuration into the requested type. + pub fn try_into<'de, T: Deserialize<'de>>(self) -> Result { + T::deserialize(self) + } } impl Source for Config { diff --git a/src/de.rs b/src/de.rs index 2ff49e9..3e86bd2 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,6 +1,7 @@ use serde::de; use value::{Value, ValueKind, ValueWithKey}; use error::*; +use config::Config; use std::borrow::Cow; use std::iter::Peekable; use std::collections::HashMap; @@ -325,3 +326,114 @@ impl<'de> de::MapAccess<'de> for MapAccess { de::DeserializeSeed::deserialize(seed, self.elements.remove(0).1) } } + +impl<'de> de::Deserializer<'de> for Config { + type Error = ConfigError; + + #[inline] + fn deserialize_any(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + // Deserialize based on the underlying type + match self.cache.kind { + ValueKind::Nil => visitor.visit_unit(), + ValueKind::Integer(i) => visitor.visit_i64(i), + ValueKind::Boolean(b) => visitor.visit_bool(b), + ValueKind::Float(f) => visitor.visit_f64(f), + ValueKind::String(s) => visitor.visit_string(s), + ValueKind::Array(values) => visitor.visit_seq(SeqAccess::new(values)), + ValueKind::Table(map) => visitor.visit_map(MapAccess::new(map)), + } + } + + #[inline] + fn deserialize_bool>(self, visitor: V) -> Result { + visitor.visit_bool(self.cache.into_bool()?) + } + + #[inline] + fn deserialize_i8>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_i8(self.cache.into_int()? as i8) + } + + #[inline] + fn deserialize_i16>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_i16(self.cache.into_int()? as i16) + } + + #[inline] + fn deserialize_i32>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_i32(self.cache.into_int()? as i32) + } + + #[inline] + fn deserialize_i64>(self, visitor: V) -> Result { + visitor.visit_i64(self.cache.into_int()?) + } + + #[inline] + fn deserialize_u8>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_u8(self.cache.into_int()? as u8) + } + + #[inline] + fn deserialize_u16>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_u16(self.cache.into_int()? as u16) + } + + #[inline] + fn deserialize_u32>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_u32(self.cache.into_int()? as u32) + } + + #[inline] + fn deserialize_u64>(self, visitor: V) -> Result { + // FIXME: This should *fail* if the value does not fit in the requets integer type + visitor.visit_u64(self.cache.into_int()? as u64) + } + + #[inline] + fn deserialize_f32>(self, visitor: V) -> Result { + visitor.visit_f32(self.cache.into_float()? as f32) + } + + #[inline] + fn deserialize_f64>(self, visitor: V) -> Result { + visitor.visit_f64(self.cache.into_float()?) + } + + #[inline] + fn deserialize_str>(self, visitor: V) -> Result { + visitor.visit_string(self.cache.into_str()?) + } + + #[inline] + fn deserialize_string>(self, visitor: V) -> Result { + visitor.visit_string(self.cache.into_str()?) + } + + #[inline] + fn deserialize_option(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + // Match an explicit nil as None and everything else as Some + match self.cache.kind { + ValueKind::Nil => visitor.visit_none(), + _ => visitor.visit_some(self), + } + } + + forward_to_deserialize_any! { + char seq + bytes byte_buf map struct unit enum newtype_struct + identifier ignored_any unit_struct tuple_struct tuple + } +} -- cgit v1.2.3