summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2022-03-02 13:07:51 +0100
committerGitHub <noreply@github.com>2022-03-02 13:07:51 +0100
commit2e9ccf751dddf1f52b4634dc1ef4ed954cf0123e (patch)
tree9887080036cb7d2035d1cd475331db75410b9f92
parent7951a750942a859f5b816198f48677823f307d04 (diff)
parent53322d4ca277a2e55a7efeb654e400e5c05eb672 (diff)
Merge pull request #255 from simon-an/parse-environment-list
feat: env contains list of strings
-rw-r--r--examples/env-list/main.rs25
-rw-r--r--src/env.rs53
-rw-r--r--tests/env.rs86
3 files changed, 158 insertions, 6 deletions
diff --git a/examples/env-list/main.rs b/examples/env-list/main.rs
new file mode 100644
index 0000000..f567419
--- /dev/null
+++ b/examples/env-list/main.rs
@@ -0,0 +1,25 @@
+use config::Config;
+#[derive(Debug, Default, serde_derive::Deserialize, PartialEq)]
+struct AppConfig {
+ list: Vec<String>,
+}
+
+fn main() {
+ std::env::set_var("APP_LIST", "Hello World");
+
+ let config = Config::builder()
+ .add_source(
+ config::Environment::with_prefix("APP")
+ .try_parsing(true)
+ .separator("_")
+ .list_separator(" "),
+ )
+ .build()
+ .unwrap();
+
+ let app: AppConfig = config.try_deserialize().unwrap();
+
+ assert_eq!(app.list, vec![String::from("Hello"), String::from("World")]);
+
+ std::env::remove_var("APP_LIST");
+}
diff --git a/src/env.rs b/src/env.rs
index 9968106..03d5785 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -5,6 +5,7 @@ use crate::map::Map;
use crate::source::Source;
use crate::value::{Value, ValueKind};
+#[must_use]
#[derive(Clone, Debug, Default)]
pub struct Environment {
/// Optional prefix that will limit access to the environment to only keys that
@@ -24,6 +25,12 @@ pub struct Environment {
/// an environment key of `REDIS_PASSWORD` to match.
separator: Option<String>,
+ /// Optional character sequence that separates each env value into a vector. only works when try_parsing is set to true
+ /// Once set, you cannot have type String on the same environment, unless you set list_parse_keys.
+ list_separator: Option<String>,
+ /// A list of keys which should always be parsed as a list. If not set you can have only Vec<String> or String (not both) in one environment.
+ list_parse_keys: Option<Vec<String>>,
+
/// Ignore empty env values (treat as unset).
ignore_empty: bool,
@@ -80,25 +87,43 @@ impl Environment {
}
}
- #[must_use]
pub fn prefix(mut self, s: &str) -> Self {
self.prefix = Some(s.into());
self
}
- #[must_use]
pub fn prefix_separator(mut self, s: &str) -> Self {
self.prefix_separator = Some(s.into());
self
}
- #[must_use]
pub fn separator(mut self, s: &str) -> Self {
self.separator = Some(s.into());
self
}
- #[must_use]
+ /// When set and try_parsing is true, then all environment variables will be parsed as [`Vec<String>`] instead of [`String`].
+ /// See [`with_list_parse_key`] when you want to use [`Vec<String>`] in combination with [`String`].
+ pub fn list_separator(mut self, s: &str) -> Self {
+ self.list_separator = Some(s.into());
+ self
+ }
+
+ /// Add a key which should be parsed as a list when collecting [`Value`]s from the environment.
+ /// Once list_separator is set, the type for string is [`Vec<String>`].
+ /// To switch the default type back to type Strings you need to provide the keys which should be [`Vec<String>`] using this function.
+ pub fn with_list_parse_key(mut self, key: &str) -> Self {
+ if self.list_parse_keys == None {
+ self.list_parse_keys = Some(vec![key.into()])
+ } else {
+ self.list_parse_keys = self.list_parse_keys.map(|mut keys| {
+ keys.push(key.into());
+ keys
+ });
+ }
+ self
+ }
+
pub fn ignore_empty(mut self, ignore: bool) -> Self {
self.ignore_empty = ignore;
self
@@ -106,13 +131,11 @@ impl Environment {
/// Note: enabling `try_parsing` can reduce performance it will try and parse
/// each environment variable 3 times (bool, i64, f64)
- #[must_use]
pub fn try_parsing(mut self, try_parsing: bool) -> Self {
self.try_parsing = try_parsing;
self
}
- #[must_use]
pub fn source(mut self, source: Option<Map<String, String>>) -> Self {
self.source = source;
self
@@ -173,6 +196,24 @@ impl Source for Environment {
ValueKind::I64(parsed)
} else if let Ok(parsed) = value.parse::<f64>() {
ValueKind::Float(parsed)
+ } else if let Some(separator) = &self.list_separator {
+ if let Some(keys) = &self.list_parse_keys {
+ if keys.contains(&key) {
+ let v: Vec<Value> = value
+ .split(separator)
+ .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_string())))
+ .collect();
+ ValueKind::Array(v)
+ } else {
+ ValueKind::String(value)
+ }
+ } else {
+ let v: Vec<Value> = value
+ .split(separator)
+ .map(|s| Value::new(Some(&uri), ValueKind::String(s.to_string())))
+ .collect();
+ ValueKind::Array(v)
+ }
} else {
ValueKind::String(value)
}
diff --git a/tests/env.rs b/tests/env.rs
index 90852e0..fcadf81 100644
--- a/tests/env.rs
+++ b/tests/env.rs
@@ -393,6 +393,56 @@ fn test_parse_bool_fail() {
}
#[test]
+fn test_parse_string_and_list() {
+ // using a struct in an enum here to make serde use `deserialize_any`
+ #[derive(Deserialize, Debug)]
+ #[serde(tag = "tag")]
+ enum TestStringEnum {
+ String(TestString),
+ }
+
+ #[derive(Deserialize, Debug)]
+ struct TestString {
+ string_val: String,
+ string_list: Vec<String>,
+ }
+
+ env::set_var("LIST_STRING_LIST", "test,string");
+ env::set_var("LIST_STRING_VAL", "test,string");
+
+ let environment = Environment::default()
+ .prefix("LIST")
+ .list_separator(",")
+ .with_list_parse_key("string_list")
+ .try_parsing(true);
+
+ let config = Config::builder()
+ .set_default("tag", "String")
+ .unwrap()
+ .add_source(environment)
+ .build()
+ .unwrap();
+
+ let config: TestStringEnum = config.try_deserialize().unwrap();
+
+ match config {
+ TestStringEnum::String(TestString {
+ string_val,
+ string_list,
+ }) => {
+ assert_eq!(String::from("test,string"), string_val);
+ assert_eq!(
+ vec![String::from("test"), String::from("string")],
+ string_list
+ );
+ }
+ }
+
+ env::remove_var("LIST_STRING_VAL");
+ env::remove_var("LIST_STRING_LIST");
+}
+
+#[test]
fn test_parse_string() {
// using a struct in an enum here to make serde use `deserialize_any`
#[derive(Deserialize, Debug)]
@@ -429,6 +479,42 @@ fn test_parse_string() {
}
#[test]
+fn test_parse_string_list() {
+ // using a struct in an enum here to make serde use `deserialize_any`
+ #[derive(Deserialize, Debug)]
+ #[serde(tag = "tag")]
+ enum TestListEnum {
+ StringList(TestList),
+ }
+
+ #[derive(Deserialize, Debug)]
+ struct TestList {
+ string_list: Vec<String>,
+ }
+
+ env::set_var("STRING_LIST", "test string");
+
+ let environment = Environment::default().try_parsing(true).list_separator(" ");
+
+ let config = Config::builder()
+ .set_default("tag", "StringList")
+ .unwrap()
+ .add_source(environment)
+ .build()
+ .unwrap();
+
+ let config: TestListEnum = config.try_deserialize().unwrap();
+
+ let test_string = vec![String::from("test"), String::from("string")];
+
+ match config {
+ TestListEnum::StringList(TestList { string_list }) => assert_eq!(test_string, string_list),
+ }
+
+ env::remove_var("STRING_LIST");
+}
+
+#[test]
fn test_parse_off_string() {
// using a struct in an enum here to make serde use `deserialize_any`
#[derive(Deserialize, Debug)]