Skip to content

Commit

Permalink
Build on previous commits to define extract(_inner)_lossy.
Browse files Browse the repository at this point in the history
These functions behave the same as extract(_inner), but they
accept appropriate strings and numbers in places of booleans,
and appropriate strings in place of numbers.
  • Loading branch information
nmathewson committed Apr 2, 2024
1 parent 2eef394 commit 84d0de4
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 1 deletion.
87 changes: 86 additions & 1 deletion src/figment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::de::Deserialize;

use crate::{Profile, Provider, Metadata};
use crate::error::{Kind, Result};
use crate::value::{Value, Map, Dict, Tag, ConfiguredValueDe, DefaultPrimHandler};
use crate::value::{Value, Map, Dict, Tag, ConfiguredValueDe, DefaultPrimHandler, LossyPrimHandler};
use crate::coalesce::{Coalescible, Order};

/// Combiner of [`Provider`]s for configuration value extraction.
Expand Down Expand Up @@ -485,6 +485,60 @@ impl Figment {
T::deserialize(ConfiguredValueDe::<'_,DefaultPrimHandler>::from(self, &self.merged()?))
}

/// As [`extract`](Figment::extract_lossy), but interpret numbers and booleans more flexibly.
///
/// See [`Value::to_bool_lossy`] and [`Value::to_num_lossy`] for a full explanation
/// of the imputs accepted.
///
///
/// # Example
///
/// ```rust
/// use serde::Deserialize;
///
/// use figment::{Figment, providers::{Format, Toml, Json, Env}};
///
/// #[derive(Debug, PartialEq, Deserialize)]
/// struct Config {
/// name: String,
/// numbers: Option<Vec<usize>>,
/// debug: bool,
/// }
///
/// figment::Jail::expect_with(|jail| {
/// jail.create_file("Config.toml", r#"
/// name = "test"
/// numbers = ["1", "2", "3", "10"]
/// "#)?;
///
/// jail.set_env("config_name", "env-test");
///
/// jail.create_file("Config.json", r#"
/// {
/// "name": "json-test",
/// "debug": "yes"
/// }
/// "#)?;
///
/// let config: Config = Figment::new()
/// .merge(Toml::file("Config.toml"))
/// .merge(Env::prefixed("CONFIG_"))
/// .join(Json::file("Config.json"))
/// .extract_lossy()?;
///
/// assert_eq!(config, Config {
/// name: "env-test".into(),
/// numbers: vec![1, 2, 3, 10].into(),
/// debug: true
/// });
///
/// Ok(())
/// });
/// ```
pub fn extract_lossy<'a, T: Deserialize<'a>>(&self) -> Result<T> {
T::deserialize(ConfiguredValueDe::<'_,LossyPrimHandler>::from(self, &self.merged()?))
}

/// Deserializes the value at the `key` path in the collected value into
/// `T`.
///
Expand Down Expand Up @@ -514,6 +568,37 @@ impl Figment {
T::deserialize(ConfiguredValueDe::<'_,DefaultPrimHandler>::from(self, &self.find_value(key)?))
}

/// As [`extract`](Figment::extract_lossy), but interpret numbers and booleans more flexibly.
///
/// See [`Value::to_bool_lossy`] and [`Value::to_num_lossy`] for a full explanation
/// of the imputs accepted.
///
/// # Example
///
/// ```rust
/// use figment::{Figment, providers::{Format, Toml, Json}};
///
/// figment::Jail::expect_with(|jail| {
/// jail.create_file("Config.toml", r#"
/// numbers = ["1", "2", "3", "10"]
/// "#)?;
///
/// jail.create_file("Config.json", r#"{ "debug": true } "#)?;
///
/// let numbers: Vec<usize> = Figment::new()
/// .merge(Toml::file("Config.toml"))
/// .join(Json::file("Config.json"))
/// .extract_inner_lossy("numbers")?;
///
/// assert_eq!(numbers, vec![1, 2, 3, 10]);
///
/// Ok(())
/// });
/// ```
pub fn extract_inner_lossy<'a, T: Deserialize<'a>>(&self, key: &str) -> Result<T> {
T::deserialize(ConfiguredValueDe::<'_,LossyPrimHandler>::from(self, &self.find_value(key)?))
}

/// Returns an iterator over the metadata for all of the collected values in
/// the order in which they were added to `self`.
///
Expand Down
19 changes: 19 additions & 0 deletions src/value/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ impl PrimHandler for DefaultPrimHandler {
}
}

#[derive(Default)]
pub struct LossyPrimHandler{}
impl PrimHandler for LossyPrimHandler {
fn interpret_as_bool(v: &Value) -> Result<Cow<'_, Value>> {
Ok(if let Some(b) = v.to_bool_lossy() {
Cow::Owned(Value::Bool(v.tag(), b))
} else {
Cow::Borrowed(v)
})
}
fn interpret_as_num(v: &Value) -> Result<Cow<'_, Value>> {
Ok(if let Some(n) = v.to_num_lossy() {
Cow::Owned(Value::Num(v.tag(), n))
} else {
Cow::Borrowed(v)
})
}
}

pub struct ConfiguredValueDe<'c, PH=DefaultPrimHandler> {
pub config: &'c Figment,
pub value: &'c Value,
Expand Down
28 changes: 28 additions & 0 deletions tests/lossy_values.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use serde::Deserialize;
use figment::{Figment, providers::{Toml, Format}};

#[derive(Debug, Deserialize, PartialEq)]
struct Config {
bs: Vec<bool>,
u8s: Vec<u8>,
i32s: Vec<i32>,
f64s: Vec<f64>,
}

static TOML: &str = r##"
u8s = [1, 2, 3, "4", 5, "6"]
i32s = [-1, -2, 3, "-4", 5, "6"]
f64s = [1, "2", -3, -4.5, "5.0", "-6.0"]
bs = [true, false, "true", "false", "YES", "no", "on", "OFF", "1", "0", 1, 0]
"##;

#[test]
fn lossy_values() {
let config: Config = Figment::from(Toml::string(TOML)).extract_lossy().unwrap();
assert_eq!(&config.u8s, &[ 1, 2, 3, 4, 5, 6 ]);
assert_eq!(&config.i32s, &[-1, -2, 3, -4, 5, 6]);
assert_eq!(&config.f64s, &[1.0, 2.0, -3.0, -4.5, 5.0, -6.0]);
assert_eq!(&config.bs, &[
true, false, true, false, true, false, true, false, true, false, true, false
]);
}

0 comments on commit 84d0de4

Please sign in to comment.