diff --git a/src/env.rs b/src/env.rs index 7da8c937..9c52e5ff 100644 --- a/src/env.rs +++ b/src/env.rs @@ -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 .split(separator) .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_owned()))) @@ -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; +} diff --git a/tests/testsuite/env.rs b/tests/testsuite/env.rs index f8ef2b23..969faede 100644 --- a/tests/testsuite/env.rs +++ b/tests/testsuite/env.rs @@ -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_val: String, + } + + #[derive(Deserialize, Debug, PartialEq)] + struct TestConfig { + nested: Vec, + } + + 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`