Skip to content

Commit

Permalink
Merge pull request #39 from LukasKalbertodt/empty-string-env-is-unset…
Browse files Browse the repository at this point in the history
…-sometimes

Treat deserialization errors from empty env vars as unset env var
  • Loading branch information
LukasKalbertodt authored Oct 18, 2024
2 parents ce3b266 + 06db476 commit ed402c4
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 5 deletions.
13 changes: 9 additions & 4 deletions src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,18 @@ pub fn from_env_with_parser<T, E: std::error::Error + Send + Sync + 'static>(
parse: fn(&str) -> Result<T, E>,
) -> Result<Option<T>, Error> {
let v = get_env_var!(key, field);
parse(&v)
.map(Some)
.map_err(|err| {
let is_empty = v.is_empty();
match parse(&v) {
Ok(v) => Ok(Some(v)),
Err(_) if is_empty => Ok(None),
Err(err) => Err(
ErrorInner::EnvParseError {
field: field.to_owned(),
key: key.to_owned(),
err: Box::new(err),
}.into()
})
),
}
}

pub fn from_env_with_deserializer<T>(
Expand All @@ -83,9 +86,11 @@ pub fn from_env_with_deserializer<T>(
deserialize: fn(crate::env::Deserializer) -> Result<T, crate::env::DeError>,
) -> Result<Option<T>, Error> {
let s = get_env_var!(key, field);
let is_empty = s.is_empty();

match deserialize(crate::env::Deserializer::new(s)) {
Ok(v) => Ok(Some(v)),
Err(_) if is_empty => Ok(None),
Err(e) => Err(ErrorInner::EnvDeserialization {
key: key.into(),
field: field.into(),
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,9 @@ pub use crate::{
/// Assigns an environment variable to this field. In [`Partial::from_env`], the
/// variable is checked and deserialized into the field if present.
///
/// If the env var is set to an empty string and if the field fails to
/// parse/deserialize from it, it is treated as unset.
///
/// ### `parse_env`
///
/// ```ignore
Expand Down
47 changes: 46 additions & 1 deletion tests/env.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use serde::Deserialize;
use confique::{Config};
use confique::{Config, Partial};
use pretty_assertions::assert_eq;

#[derive(Debug, Deserialize)]
enum Foo { A, B, C }
Expand All @@ -17,3 +18,47 @@ fn enum_env() {
let conf = Conf::builder().env().load();
assert!(matches!(conf, Ok(Conf { foo: Foo::B })));
}

fn my_parser(s: &str) -> Result<u32, impl std::error::Error> {
s.trim().parse()
}

#[test]
fn empty_error_is_unset() {
#[derive(Config)]
#[config(partial_attr(derive(PartialEq, Debug)))]
#[allow(dead_code)]
struct Conf {
#[config(env = "EMPTY_ERROR_IS_UNSET_FOO")]
foo: u32,

#[config(env = "EMPTY_ERROR_IS_UNSET_BAR", parse_env = my_parser)]
bar: u32,

#[config(env = "EMPTY_ERROR_IS_UNSET_BAZ")]
baz: String,
}

type Partial = <Conf as Config>::Partial;

std::env::set_var("EMPTY_ERROR_IS_UNSET_FOO", "");
assert_eq!(Partial::from_env().unwrap(), Partial {
foo: None,
bar: None,
baz: None,
});

std::env::set_var("EMPTY_ERROR_IS_UNSET_BAR", "");
assert_eq!(Partial::from_env().unwrap(), Partial {
foo: None,
bar: None,
baz: None,
});

std::env::set_var("EMPTY_ERROR_IS_UNSET_BAZ", "");
assert_eq!(Partial::from_env().unwrap(), Partial {
foo: None,
bar: None,
baz: Some("".into()),
});
}

0 comments on commit ed402c4

Please sign in to comment.