#[cfg(all(feature = "humantime", feature = "toml"))]
mod humantime_serde {
use std::time::Duration;
use confik::{ConfigBuilder, Configuration, TomlSource};
#[test]
fn from_humantime_option() {
#[derive(Debug, PartialEq, Eq, Configuration)]
struct Config {
#[confik(forward(serde(with = "confik::humantime::option")))]
timeout: Option<Duration>,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("timeout = \"1h 42m\""))
.try_build()
.unwrap();
assert_eq!(
config,
Config {
timeout: Some(Duration::from_secs(6_120))
}
);
}
}
#[cfg(all(feature = "secrecy", feature = "toml"))]
mod secrecy {
use confik::{Configuration, TomlSource};
use indoc::indoc;
use secrecy::{ExposeSecret as _, SecretString};
#[test]
fn secret_string() {
#[derive(Debug, Configuration)]
struct Config {
#[confik(secret)]
secret_string: SecretString,
}
let toml = indoc! {r#"
secret_string = "SeriouslySecret"
"#};
let config = Config::builder()
.override_with(TomlSource::new(toml).allow_secrets())
.try_build()
.unwrap();
assert_eq!(
format!("{:?}", config.secret_string),
"SecretBox<str>([REDACTED])",
);
assert_eq!(
format!("{config:?}"),
"Config { secret_string: SecretBox<str>([REDACTED]) }",
);
assert_eq!(config.secret_string.expose_secret(), "SeriouslySecret");
}
#[test]
fn secret_string_in_field_not_marked_secret() {
#[derive(Debug, Configuration)]
struct Config {
secret_string: SecretString,
}
let toml = indoc! {r#"
secret_string = "SeriouslySecret"
"#};
Config::builder()
.override_with(TomlSource::new(toml))
.try_build()
.unwrap_err();
let config = Config::builder()
.override_with(TomlSource::new(toml).allow_secrets())
.try_build()
.unwrap();
assert_eq!(
format!("{:?}", config.secret_string),
"SecretBox<str>([REDACTED])",
);
assert_eq!(
format!("{config:?}"),
"Config { secret_string: SecretBox<str>([REDACTED]) }",
);
assert_eq!(config.secret_string.expose_secret(), "SeriouslySecret");
}
}
#[cfg(all(feature = "bigdecimal", feature = "toml"))]
mod bigdecimal {
use std::str::FromStr;
use bigdecimal::BigDecimal;
use confik::{Configuration, Error, TomlSource};
use indoc::formatdoc;
#[derive(Configuration, Debug)]
struct Config {
big_decimal: BigDecimal,
}
#[test]
fn bigdecimal() {
let big_decimal = "1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573";
let toml = formatdoc! {r#"
big_decimal = "{big_decimal}"
"#};
let config = Config::builder()
.override_with(TomlSource::new(toml))
.try_build()
.expect("Failed to parse config");
assert_eq!(
config.big_decimal,
BigDecimal::from_str(big_decimal).unwrap()
);
}
#[test]
fn bigdecimal_missing_err_propagation() {
let toml = formatdoc! {r#"
big_decimal = ""
"#};
let config_parsing_err = Config::builder()
.override_with(TomlSource::new(toml))
.try_build();
match config_parsing_err {
Ok(_) => {
panic!("Expected parsing error");
}
Err(err) => match err {
Error::Source(source_err, _config) => {
assert!(source_err
.to_string()
.contains("Failed to parse empty string"));
assert!(source_err.to_string().contains("big_decimal"));
}
_ => {
panic!("Expected MissingValue error");
}
},
}
}
}
#[cfg(feature = "serde_json")]
mod serde_json {
#[cfg(feature = "toml")]
mod toml {
use confik::{Configuration, TomlSource};
use serde_json::{json, Value};
#[derive(Configuration)]
struct Root {
leaf: Leaf,
}
#[derive(Configuration)]
struct Leaf {
data: Value,
}
#[test]
fn value_loads_from_toml() {
let toml = r#"
[leaf.data]
key = "hello"
num = 42
"#;
let config = Root::builder()
.override_with(TomlSource::new(toml))
.try_build()
.unwrap();
assert_eq!(config.leaf.data, json!({"key": "hello", "num": 42}));
}
#[test]
fn objects_merged_from_multiple_sources() {
let toml_1 = r#"
[leaf.data]
item_1 = 1
"#;
let toml_2 = r#"
[leaf.data]
item_2 = 2
"#;
let config = Root::builder()
.override_with(TomlSource::new(toml_1))
.override_with(TomlSource::new(toml_2))
.try_build()
.unwrap();
assert_eq!(config.leaf.data, json!({"item_1": 1, "item_2": 2}));
}
#[test]
fn object_merged_missing_in_first_source() {
let toml_1 = r#"
[leaf.data]
item_2 = 2
"#;
let toml_2 = "[leaf]";
let config = Root::builder()
.override_with(TomlSource::new(toml_1))
.override_with(TomlSource::new(toml_2))
.try_build()
.unwrap();
assert_eq!(config.leaf.data, json!({ "item_2": 2 }));
}
}
}
#[cfg(all(feature = "jiff-0_2", feature = "toml"))]
mod jiff {
use confik::{ConfigBuilder, Configuration, TomlSource};
use jiff_0_2::{civil, SignedDuration, Span, Timestamp};
#[test]
fn timestamp_second_optional() {
#[derive(Debug, PartialEq, Eq, Configuration)]
struct Config {
#[confik(forward(serde(with = "confik::jiff::timestamp::second::optional")))]
ts: Option<Timestamp>,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("ts = 1517644800"))
.try_build()
.unwrap();
assert_eq!(config.ts, Some(Timestamp::from_second(1517644800).unwrap()));
}
#[test]
fn timestamp_millisecond_optional() {
#[derive(Debug, PartialEq, Eq, Configuration)]
struct Config {
#[confik(forward(serde(with = "confik::jiff::timestamp::millisecond::optional")))]
ts: Option<Timestamp>,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("ts = 1517644800123"))
.try_build()
.unwrap();
assert_eq!(
config.ts,
Some(Timestamp::from_millisecond(1_517_644_800_123).unwrap())
);
}
#[test]
fn unsigned_duration_optional() {
use std::time::Duration;
#[derive(Debug, PartialEq, Eq, Configuration)]
struct Config {
#[confik(forward(serde(with = "confik::jiff::unsigned_duration::optional")))]
timeout: Option<Duration>,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("timeout = \"PT1H42M\""))
.try_build()
.unwrap();
assert_eq!(config.timeout, Some(Duration::from_secs(6_120)));
}
#[test]
fn civil_date() {
#[derive(Debug, PartialEq, Eq, Configuration)]
struct Config {
date: civil::Date,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("date = \"2013-08-09\""))
.try_build()
.unwrap();
assert_eq!(config.date, civil::Date::constant(2013, 8, 9));
}
#[test]
fn civil_time() {
#[derive(Debug, PartialEq, Eq, Configuration)]
struct Config {
time: civil::Time,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("time = \"10:30:00\""))
.try_build()
.unwrap();
assert_eq!(config.time, civil::Time::constant(10, 30, 0, 0));
}
#[test]
fn timestamp() {
#[derive(Debug, PartialEq, Eq, Configuration)]
struct Config {
ts: Timestamp,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("ts = \"2024-01-01T00:00:00Z\""))
.try_build()
.unwrap();
assert_eq!(
config.ts,
"2024-01-01T00:00:00Z".parse::<Timestamp>().unwrap()
);
}
#[test]
fn span_humantime_style_parsing() {
#[derive(Debug, Configuration)]
struct Config {
timeout: Span,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("timeout = \"1h 42m\""))
.try_build()
.unwrap();
assert_eq!(
jiff_0_2::SpanFieldwise(config.timeout),
jiff_0_2::SpanFieldwise(Span::new().hours(1).minutes(42)),
);
}
#[test]
fn signed_duration_humantime_style_parsing() {
#[derive(Debug, PartialEq, Eq, Configuration)]
struct Config {
timeout: SignedDuration,
}
let config = ConfigBuilder::<Config>::default()
.override_with(TomlSource::new("timeout = \"1h 42m\""))
.try_build()
.unwrap();
assert_eq!(config.timeout, SignedDuration::from_secs(6_120),);
}
}
#[cfg(feature = "js_option")]
mod js_option {
use confik::Configuration;
use js_option::JsOption;
#[derive(Configuration, Debug)]
struct Config {
opt: JsOption<usize>,
}
#[test]
fn undefined() {
let config = Config::builder()
.try_build()
.expect("Should be valid without config");
assert_eq!(config.opt, JsOption::Undefined);
}
#[cfg(feature = "json")]
#[test]
fn null() {
let json = r#"{ "opt": null }"#;
let config = Config::builder()
.override_with(confik::JsonSource::new(json))
.try_build()
.expect("Failed to parse config");
assert_eq!(config.opt, JsOption::Null);
}
#[cfg(feature = "toml")]
#[test]
fn present() {
let toml = "opt = 5";
let config = Config::builder()
.override_with(confik::TomlSource::new(toml))
.try_build()
.expect("Should be valid without config");
assert_eq!(config.opt, JsOption::Some(5));
}
#[cfg(feature = "json")]
#[test]
fn merge() {
#[derive(Debug, Configuration, PartialEq, Eq)]
struct Config {
one: JsOption<usize>,
two: JsOption<usize>,
three: JsOption<usize>,
four: JsOption<usize>,
}
let base = r#"{ "two": null, "three": 5 }"#;
let merge = r#"{ "one": 1, "two": 2, "three": 3}"#;
let config = Config::builder()
.override_with(confik::JsonSource::new(merge))
.override_with(confik::JsonSource::new(base))
.try_build()
.expect("Failed to parse config");
assert_eq!(
config,
Config {
one: JsOption::Some(1),
two: JsOption::Null,
three: JsOption::Some(5),
four: JsOption::Undefined,
}
);
}
}