Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ impl Source for Environment {
ValueKind::Float(parsed)
} else if let Some(separator) = &self.list_separator {
if let Some(keys) = &self.list_parse_keys {
if keys.contains(&key) {
if keys.iter().any(|k| wildcard_match(k, &key)) {
let v: Vec<Value> = value
.split(separator)
.map(|s| Value::new(Some(&uri), ValueKind::String(s.to_owned())))
Expand Down Expand Up @@ -340,3 +340,34 @@ impl Source for Environment {
Ok(m)
}
}

fn wildcard_match(pattern: &str, input: &str) -> bool {
if pattern == "" {
return pattern == input;
}
if pattern == "*" {
return true;
}
deep_wildcard_match(input.as_bytes(), pattern.as_bytes())
}

fn deep_wildcard_match(input: &[u8], pattern: &[u8]) -> bool {
let mut input = input;
let mut pattern = pattern;
while pattern.len() > 0 {
match pattern[0] {
b'*' => {
return deep_wildcard_match(input, &pattern[1..])
|| (input.len() > 0 && deep_wildcard_match(&input[1..], pattern))
}
_ => {
if input.len() == 0 || input[0] != pattern[0] {
return false;
}
}
}
input = &input[1..];
pattern = &pattern[1..];
}
return input.len() == 0 && pattern.len() == 0;
}
50 changes: 50 additions & 0 deletions tests/testsuite/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,56 @@ fn test_parse_string_and_list() {
);
}

#[test]
fn test_parse_string_and_list_with_wildcard() {
#[derive(Deserialize, Debug, PartialEq)]
struct Nested {
list_val: Vec<String>,
string_val: String,
}

#[derive(Deserialize, Debug, PartialEq)]
struct TestConfig {
nested: Vec<Nested>,
}

temp_env::with_vars(
vec![
("list_nested[0].list_val", Some("test0,string0")),
("list_nested[0].string_val", Some("test0,string0")),
("list_nested[1].list_val", Some("test1,string1")),
("list_nested[1].string_val", Some("test1,string1")),
],
|| {
let environment = Environment::default()
.prefix("LIST")
.list_separator(",")
.with_list_parse_key("nested[*].list_val")
.try_parsing(true);

let config = Config::builder().add_source(environment).build().unwrap();

let config: TestConfig = config.try_deserialize().unwrap();

assert_eq!(
config,
TestConfig {
nested: vec![
Nested {
list_val: vec![String::from("test0"), String::from("string0")],
string_val: String::from("test0,string0"),
},
Nested {
list_val: vec![String::from("test1"), String::from("string1")],
string_val: String::from("test1,string1"),
},
]
}
)
},
);
}

#[test]
fn test_parse_string_and_list_ignore_list_parse_key_case() {
// using a struct in an enum here to make serde use `deserialize_any`
Expand Down