summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/msrv.yml8
-rw-r--r--CHANGELOG.md14
-rw-r--r--Cargo.toml8
-rw-r--r--examples/watch/main.rs19
-rw-r--r--src/builder.rs4
-rw-r--r--src/de.rs104
-rw-r--r--src/file/mod.rs3
-rw-r--r--src/value.rs27
-rw-r--r--tests/env.rs4
-rw-r--r--tests/file_json5.rs2
-rw-r--r--tests/integer_range.rs10
-rw-r--r--tests/unsigned_int.rs48
-rw-r--r--tests/unsigned_int_hm.rs46
13 files changed, 223 insertions, 74 deletions
diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml
index fa9cd47..e069266 100644
--- a/.github/workflows/msrv.yml
+++ b/.github/workflows/msrv.yml
@@ -111,12 +111,6 @@ jobs:
needs: [check]
name: Clippy
runs-on: ubuntu-latest
- strategy:
- matrix:
- rust:
- - stable
- - beta
- - nightly
steps:
- name: Checkout sources
uses: actions/checkout@v3.0.2
@@ -124,7 +118,7 @@ jobs:
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
- toolchain: ${{ matrix.rust }}
+ toolchain: 1.56.1
override: true
components: clippy
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d086c72..daadbc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
+## 0.13.2 - 2022-08-02
+
+ - Backport of [#316] to be testing with temp_env. The backport was necessary to
+ be able to backport the next change. This change shouldn't be user-visible.
+ - Backport of [#353] to use TryInto for more permissive deserialization of
+ integers
+ - Backport of commit [518a3cafa1e62ba7405709e5c508247e328e0a18] from [#362] to
+ fix tests
+
+[#316]: https://github.com/mehcode/config-rs/pull/316
+[#353]: https://github.com/mehcode/config-rs/pull/353
+[518a3cafa1e62ba7405709e5c508247e328e0a18]: https://github.com/mehcode/config-rs/commit/518a3cafa1e62ba7405709e5c508247e328e0a18
+[#362]: https://github.com/mehcode/config-rs/pull/362
+
## 0.13.1 - 2022-04-13
- typo in doc comment for ConfigBuilder [#299]
diff --git a/Cargo.toml b/Cargo.toml
index 79a3fc8..e2657ad 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,7 +32,7 @@ toml = { version = "0.5", optional = true }
serde_json = { version = "1.0.2", optional = true }
yaml-rust = { version = "0.4", optional = true }
rust-ini = { version = "0.18", optional = true }
-ron = { version = "0.7", optional = true }
+ron = { version = "0.8", optional = true }
json5_rs = { version = "0.4", optional = true, package = "json5" }
indexmap = { version = "1.7.0", features = ["serde-1"], optional = true}
pathdiff = "0.2"
@@ -42,12 +42,12 @@ serde_derive = "1.0.8"
float-cmp = "0.9"
chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "fs", "io-util", "time"]}
-warp = "=0.3.1"
+warp = "=0.3.2"
futures = "0.3.15"
reqwest = "0.11.10"
serde = "1.0"
glob = "0.3"
lazy_static = "1"
-notify = "^4.0.0"
-temp-env = "0.2.0"
+notify = "^5.0.0"
+temp-env = "0.3.0"
diff --git a/examples/watch/main.rs b/examples/watch/main.rs
index 801ee4f..ca83572 100644
--- a/examples/watch/main.rs
+++ b/examples/watch/main.rs
@@ -1,7 +1,8 @@
#![allow(deprecated)]
use config::{Config, File};
-use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
+use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::collections::HashMap;
+use std::path::Path;
use std::sync::mpsc::channel;
use std::sync::RwLock;
use std::time::Duration;
@@ -33,19 +34,29 @@ fn watch() {
// Automatically select the best implementation for your platform.
// You can also access each implementation directly e.g. INotifyWatcher.
- let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2)).unwrap();
+ let mut watcher: RecommendedWatcher = Watcher::new(
+ tx,
+ notify::Config::default().with_poll_interval(Duration::from_secs(2)),
+ )
+ .unwrap();
// Add a path to be watched. All files and directories at that path and
// below will be monitored for changes.
watcher
- .watch("examples/watch/Settings.toml", RecursiveMode::NonRecursive)
+ .watch(
+ Path::new("examples/watch/Settings.toml"),
+ RecursiveMode::NonRecursive,
+ )
.unwrap();
// This is a simple loop, but you may want to use more complex logic here,
// for example to handle I/O.
loop {
match rx.recv() {
- Ok(DebouncedEvent::Write(_)) => {
+ Ok(Ok(Event {
+ kind: notify::event::EventKind::Modify(_),
+ ..
+ })) => {
println!(" * Settings.toml written; refreshing configuration ...");
SETTINGS.write().unwrap().refresh().unwrap();
show();
diff --git a/src/builder.rs b/src/builder.rs
index 6f928c6..df0b728 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -87,6 +87,7 @@ use crate::{config::Config, path::Expression, source::Source, value::Value};
/// let mut builder = ConfigBuilder::<DefaultState>::default();
/// ```
#[derive(Debug, Clone, Default)]
+#[must_use]
pub struct ConfigBuilder<St: BuilderState> {
defaults: Map<Expression, Value>,
overrides: Map<Expression, Value>,
@@ -201,7 +202,6 @@ impl ConfigBuilder<DefaultState> {
/// Registers new [`Source`] in this builder.
///
/// Calling this method does not invoke any I/O. [`Source`] is only saved in internal register for later use.
- #[must_use]
pub fn add_source<T>(mut self, source: T) -> Self
where
T: Source + Send + Sync + 'static,
@@ -291,7 +291,6 @@ impl ConfigBuilder<AsyncState> {
/// Registers new [`Source`] in this builder.
///
/// Calling this method does not invoke any I/O. [`Source`] is only saved in internal register for later use.
- #[must_use]
pub fn add_source<T>(mut self, source: T) -> Self
where
T: Source + Send + Sync + 'static,
@@ -303,7 +302,6 @@ impl ConfigBuilder<AsyncState> {
/// Registers new [`AsyncSource`] in this builder.
///
/// Calling this method does not invoke any I/O. [`AsyncSource`] is only saved in internal register for later use.
- #[must_use]
pub fn add_async_source<T>(mut self, source: T) -> Self
where
T: AsyncSource + Send + Sync + 'static,
diff --git a/src/de.rs b/src/de.rs
index 96c5b5e..8d45140 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -1,13 +1,38 @@
use std::collections::VecDeque;
+use std::convert::TryInto;
use std::iter::Enumerate;
use serde::de;
use crate::config::Config;
-use crate::error::{ConfigError, Result};
+use crate::error::{ConfigError, Result, Unexpected};
use crate::map::Map;
use crate::value::{Table, Value, ValueKind};
+macro_rules! try_convert_number {
+ (signed, $self:expr, $size:literal) => {{
+ let num = $self.into_int()?;
+ num.try_into().map_err(|_| {
+ ConfigError::invalid_type(
+ None,
+ Unexpected::I64(num),
+ concat!("an signed ", $size, " bit integer"),
+ )
+ })?
+ }};
+
+ (unsigned, $self:expr, $size:literal) => {{
+ let num = $self.into_uint()?;
+ num.try_into().map_err(|_| {
+ ConfigError::invalid_type(
+ None,
+ Unexpected::U64(num),
+ concat!("an unsigned ", $size, " bit integer"),
+ )
+ })?
+ }};
+}
+
impl<'de> de::Deserializer<'de> for Value {
type Error = ConfigError;
@@ -38,49 +63,50 @@ impl<'de> de::Deserializer<'de> for Value {
#[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)
+ let num = try_convert_number!(signed, self, "8");
+ visitor.visit_i8(num)
}
#[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)
+ let num = try_convert_number!(signed, self, "16");
+ visitor.visit_i16(num)
}
#[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)
+ let num = try_convert_number!(signed, self, "32");
+ visitor.visit_i32(num)
}
#[inline]
fn deserialize_i64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
- visitor.visit_i64(self.into_int()?)
+ let num = try_convert_number!(signed, self, "64");
+ visitor.visit_i64(num)
}
#[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_uint()? as u8)
+ let num = try_convert_number!(unsigned, self, "8");
+ visitor.visit_u8(num)
}
#[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_uint()? as u16)
+ let num = try_convert_number!(unsigned, self, "16");
+ visitor.visit_u16(num)
}
#[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_uint()? as u32)
+ let num = try_convert_number!(unsigned, self, "32");
+ visitor.visit_u32(num)
}
#[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_uint()? as u64)
+ let num = try_convert_number!(unsigned, self, "u64");
+ visitor.visit_u64(num)
}
#[inline]
@@ -345,19 +371,8 @@ impl<'de> de::Deserializer<'de> for Config {
where
V: de::Visitor<'de>,
{
- // Deserialize based on the underlying type
- match self.cache.kind {
- ValueKind::Nil => visitor.visit_unit(),
- ValueKind::I64(i) => visitor.visit_i64(i),
- ValueKind::I128(i) => visitor.visit_i128(i),
- ValueKind::U64(i) => visitor.visit_u64(i),
- ValueKind::U128(i) => visitor.visit_u128(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)),
- }
+ // Delegate deserialization to Value
+ de::Deserializer::deserialize_any(self.cache, visitor)
}
#[inline]
@@ -367,49 +382,50 @@ impl<'de> de::Deserializer<'de> for Config {
#[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)
+ let num = try_convert_number!(signed, self.cache, "8");
+ visitor.visit_i8(num)
}
#[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)
+ let num = try_convert_number!(signed, self.cache, "16");
+ visitor.visit_i16(num)
}
#[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)
+ let num = try_convert_number!(signed, self.cache, "32");
+ visitor.visit_i32(num)
}
#[inline]
fn deserialize_i64<V: de::Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
- visitor.visit_i64(self.cache.into_int()?)
+ let num = try_convert_number!(signed, self.cache, "64");
+ visitor.visit_i64(num)
}
#[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)
+ let num = try_convert_number!(unsigned, self.cache, "8");
+ visitor.visit_u8(num)
}
#[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)
+ let num = try_convert_number!(unsigned, self.cache, "16");
+ visitor.visit_u16(num)
}
#[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)
+ let num = try_convert_number!(unsigned, self.cache, "32");
+ visitor.visit_u32(num)
}
#[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)
+ let num = try_convert_number!(unsigned, self.cache, "64");
+ visitor.visit_u64(num)
}
#[inline]
diff --git a/src/file/mod.rs b/src/file/mod.rs
index 65f3fd6..4067d76 100644
--- a/src/file/mod.rs
+++ b/src/file/mod.rs
@@ -20,6 +20,7 @@ pub use self::source::string::FileSourceString;
///
/// It supports optional automatic file format discovery.
#[derive(Clone, Debug)]
+#[must_use]
pub struct File<T, F> {
source: T,
@@ -101,13 +102,11 @@ where
F: FileStoredFormat + 'static,
T: FileSource<F>,
{
- #[must_use]
pub fn format(mut self, format: F) -> Self {
self.format = Some(format);
self
}
- #[must_use]
pub fn required(mut self, required: bool) -> Self {
self.required = required;
self
diff --git a/src/value.rs b/src/value.rs
index c9536f7..dced53b 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -761,15 +761,30 @@ impl<'de> Deserialize<'de> for Value {
}
#[inline]
- fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Value, E> {
- // FIXME: This is bad
- Ok((value as i64).into())
+ fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Value, E>
+ where
+ E: ::serde::de::Error,
+ {
+ let num: i64 = value.try_into().map_err(|_| {
+ E::invalid_type(::serde::de::Unexpected::Unsigned(value), &self)
+ })?;
+ Ok(num.into())
}
#[inline]
- fn visit_u128<E>(self, value: u128) -> ::std::result::Result<Value, E> {
- // FIXME: This is bad
- Ok((value as i128).into())
+ fn visit_u128<E>(self, value: u128) -> ::std::result::Result<Value, E>
+ where
+ E: ::serde::de::Error,
+ {
+ let num: i128 = value.try_into().map_err(|_| {
+ E::invalid_type(
+ ::serde::de::Unexpected::Other(
+ format!("integer `{}` as u128", value).as_str(),
+ ),
+ &self,
+ )
+ })?;
+ Ok(num.into())
}
#[inline]
diff --git a/tests/env.rs b/tests/env.rs
index 2ee67de..3a24bde 100644
--- a/tests/env.rs
+++ b/tests/env.rs
@@ -191,7 +191,9 @@ fn test_parse_float() {
// can't use `matches!` because of float value
match config {
- TestFloatEnum::Float(TestFloat { float_val }) => assert_eq!(float_val, 42.3),
+ TestFloatEnum::Float(TestFloat { float_val }) => {
+ assert!(float_cmp::approx_eq!(f64, float_val, 42.3))
+ }
}
})
}
diff --git a/tests/file_json5.rs b/tests/file_json5.rs
index 74d06c2..bdeb3ea 100644
--- a/tests/file_json5.rs
+++ b/tests/file_json5.rs
@@ -84,7 +84,7 @@ fn test_error_parse() {
assert_eq!(
res.unwrap_err().to_string(),
format!(
- " --> 2:7\n |\n2 | ok: true␊\n | ^---\n |\n = expected null in {}",
+ " --> 2:7\n |\n2 | ok: true\n | ^---\n |\n = expected null in {}",
path_with_extension.display()
)
);
diff --git a/tests/integer_range.rs b/tests/integer_range.rs
index 7777ef2..e80a2f2 100644
--- a/tests/integer_range.rs
+++ b/tests/integer_range.rs
@@ -13,8 +13,14 @@ fn wrapping_u16() {
.build()
.unwrap();
- let port: u16 = c.get("settings.port").unwrap();
- assert_eq!(port, 464);
+ // FIXME: Can't compare ConfigError, because Unexpected are private.
+ let _port_error = c.get::<u16>("settings.port").unwrap_err();
+ /*
+ assert!(matches!(
+ Err(ConfigError::invalid_type(None, config::Unexpected::U64(66000), "an unsigned 16 bit integer"),)
+ port_error
+ ));
+ */
}
#[test]
diff --git a/tests/unsigned_int.rs b/tests/unsigned_int.rs
new file mode 100644
index 0000000..e870c8a
--- /dev/null
+++ b/tests/unsigned_int.rs
@@ -0,0 +1,48 @@
+#![cfg(feature = "preserve_order")]
+
+extern crate indexmap;
+
+#[derive(serde::Deserialize, Eq, PartialEq, Debug)]
+struct Container<T> {
+ inner: T,
+}
+
+#[derive(serde::Deserialize, Eq, PartialEq, Debug)]
+struct Unsigned {
+ unsigned: u16,
+}
+
+impl Default for Unsigned {
+ fn default() -> Self {
+ Self { unsigned: 128 }
+ }
+}
+
+impl From<Unsigned> for config::ValueKind {
+ fn from(unsigned: Unsigned) -> Self {
+ let mut properties = indexmap::IndexMap::new();
+ properties.insert(
+ "unsigned".to_string(),
+ config::Value::from(unsigned.unsigned),
+ );
+
+ Self::Table(properties)
+ }
+}
+
+#[test]
+fn test_deser_unsigned_int() {
+ let container = Container {
+ inner: Unsigned::default(),
+ };
+
+ let built = config::Config::builder()
+ .set_default("inner", Unsigned::default())
+ .unwrap()
+ .build()
+ .unwrap()
+ .try_deserialize::<Container<Unsigned>>()
+ .unwrap();
+
+ assert_eq!(container, built);
+}
diff --git a/tests/unsigned_int_hm.rs b/tests/unsigned_int_hm.rs
new file mode 100644
index 0000000..ab2a60c
--- /dev/null
+++ b/tests/unsigned_int_hm.rs
@@ -0,0 +1,46 @@
+#![cfg(not(feature = "preserve_order"))]
+
+#[derive(serde::Deserialize, Eq, PartialEq, Debug)]
+struct Container<T> {
+ inner: T,
+}
+
+#[derive(serde::Deserialize, Eq, PartialEq, Debug)]
+struct Unsigned {
+ unsigned: u16,
+}
+
+impl Default for Unsigned {
+ fn default() -> Self {
+ Self { unsigned: 128 }
+ }
+}
+
+impl From<Unsigned> for config::ValueKind {
+ fn from(unsigned: Unsigned) -> Self {
+ let mut properties = std::collections::HashMap::new();
+ properties.insert(
+ "unsigned".to_string(),
+ config::Value::from(unsigned.unsigned),
+ );
+
+ Self::Table(properties)
+ }
+}
+
+#[test]
+fn test_deser_unsigned_int_hm() {
+ let container = Container {
+ inner: Unsigned::default(),
+ };
+
+ let built = config::Config::builder()
+ .set_default("inner", Unsigned::default())
+ .unwrap()
+ .build()
+ .unwrap()
+ .try_deserialize::<Container<Unsigned>>()
+ .unwrap();
+
+ assert_eq!(container, built);
+}