Skip to content

Commit

Permalink
Merge #23
Browse files Browse the repository at this point in the history
23: fix case insensitivity with inconsistent casing r=TheNeikos a=tenuous-guidance

I've kept with using `ascii` casing for consistency with the equality check. I don't think it will cause an issue for env parsing as I'm pretty sure whilst values can be unicode the variables can't be. However it may make a difference if using `build_from_iter`. I think that's still fine given that `build_from_iter` seems mostly useful for test cases, as the library is designed for environment variable parsing.

Co-authored-by: tenuous-guidance <[email protected]>
  • Loading branch information
bors[bot] and tenuous-guidance authored Apr 5, 2023
2 parents 2368b7a + 9f9e705 commit d703dd9
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 5 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog `envious`

## Unreleased

- When case insensitive, environment variables with the same keys but different casing should be collated
- i.e. previously `database__password` and `DATABASE__username` would have conflicted and only one the username or password would be read
- When case insensitive, the prefix is also treated insensitively

## v0.2.0

The 0.2.0 version makes the following API changes:
Expand Down
26 changes: 21 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl<'a> Config<'a> {
///
/// Defaults to case insensitive.
///
/// NB: Only `struct` fields and `enum` variants are affected by case sensitivity.
/// NB: Only `struct` fields and `enum` variants, as well as any prefix provided via [`Self::with_prefix`] are affected by case sensitivity.
pub fn case_sensitive(&mut self, case_sensitive: bool) -> &mut Self {
self.case_sensitive = case_sensitive;
self
Expand Down Expand Up @@ -175,10 +175,26 @@ impl<'a> Config<'a> {
{
let values = iter.into_iter().map(|(k, v)| (k.into(), v.into()));

let values = values.filter_map(|(key, value)| {
let values = values.filter_map(|(mut key, value)| {
// When running case-insensitive we need to make sure that same key with varying casing
// would be stored in the same place. The simplest way to do this is to enforce a specific
// case.
if self.case_sensitive.not() {
key.make_ascii_lowercase();
}
let value = Value::Simple(value);

if let Some(prefix) = &self.prefix {
let stripped_key = key.strip_prefix(prefix.as_ref())?.to_owned();
// If case insensitive, then the prefix will need to match the new key case
let coerced_prefix;
let prefix = if self.case_sensitive {
prefix.as_ref()
} else {
coerced_prefix = prefix.to_ascii_lowercase();
&coerced_prefix
};

let stripped_key = key.strip_prefix(prefix)?.to_owned();
Some((stripped_key, value))
} else {
Some((key, value))
Expand Down Expand Up @@ -255,14 +271,14 @@ mod tests {
#[test]
fn convert_list_of_key_vals_to_tree() {
let input = vec![
(String::from("FOO"), Value::simple("bar")),
(String::from("FOO"), Value::simple("BAR")),
(String::from("BAZ"), Value::simple("124")),
(String::from("NESTED__FOO"), Value::simple("true")),
(String::from("NESTED__BAZ"), Value::simple("Hello")),
];

let expected = Value::Map(vec![
(String::from("FOO"), Value::simple("bar")),
(String::from("FOO"), Value::simple("BAR")),
(String::from("BAZ"), Value::simple("124")),
(
String::from("NESTED"),
Expand Down
18 changes: 18 additions & 0 deletions tests/case_sensitivity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,22 @@ fn parse_from_env() {
config.case_sensitive(false);
let root: Root = config.build_from_iter(vars).unwrap();
assert_eq!(root, expected);

// Inconsistent casing
let vars = [
("field1", "1"),
("field2", "ALSO_EMPTY"),
("Field3__field1", "2"),
("fIeld3__field2__notempty", "3"),
("fiEld3__field3", "4"),
];

// Works when case insensitive
let root: Root = config.build_from_iter(vars).unwrap();
assert_eq!(root, expected);

// Fails when case sensitive
config.case_sensitive(true);
let result: Result<Root, _> = config.build_from_iter(vars);
result.unwrap_err();
}
22 changes: 22 additions & 0 deletions tests/prefix_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,26 @@ fn parse_from_env() {
});

println!("{:#?}", config);

// When case insensitive, the same test should succeed with a lowercase prefix.
let config: Config = temp_env::with_vars(vars, || {
envious::Config::new()
.case_sensitive(false)
.with_prefix("envious_")
.build_from_env()
.unwrap()
});

println!("{:#?}", config);

// However when case sensitive, it will fail.
let result: Result<Config, _> = temp_env::with_vars(vars, || {
envious::Config::new()
.case_sensitive(true)
.with_prefix("envious_")
.build_from_env()
});
let err = result.unwrap_err();

println!("{:#?}", err);
}

0 comments on commit d703dd9

Please sign in to comment.