Repository: mehcode/config-rs (rust)

Issue:
Regression in nested environment paths
In config 0.11, our app used an environment source

```rust
Environment::with_prefix("app").separator("__")
```

Which would match environment vars like `APP_TLS__CERTIFICATE` (into `tls > certificate`).

This no longer works due to the group separator using the specified separator, as opposed to always being `_` https://github.com/mehcode/config-rs/blob/30d87a311929f5382cb4ecd1de62f20d2dddfb86/src/env.rs#L123

This forces me to update my environments to `APP__TLS__CERTIFICATE` which is not the end of the world but it slows adoption of the new version.

I'm not sure on the best solution, but maybe there could be a new `prefix_separator` field. This can be set automatically by the `separator` method too so that it doesn't break any existing 0.12 apps

Pull request:
add prefix separator
Should fix #291

Patch:
diff --git a/src/env.rs b/src/env.rs
index ef91ef8..9968106 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -16,6 +16,9 @@ pub struct Environment {
     /// For example, the key `CONFIG_DEBUG` would become `DEBUG` with a prefix of `config`.
     prefix: Option<String>,
 
+    /// Optional character sequence that separates the prefix from the rest of the key
+    prefix_separator: Option<String>,
+
     /// Optional character sequence that separates each key segment in an environment key pattern.
     /// Consider a nested configuration such as `redis.password`, a separator of `_` would allow
     /// an environment key of `REDIS_PASSWORD` to match.
@@ -83,6 +86,12 @@ impl Environment {
         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());
@@ -120,13 +129,17 @@ impl Source for Environment {
         let uri: String = "the environment".into();
 
         let separator = self.separator.as_deref().unwrap_or("");
-        let group_separator = self.separator.as_deref().unwrap_or("_");
+        let prefix_separator = match (self.prefix_separator.as_deref(), self.separator.as_deref()) {
+            (Some(pre), _) => pre,
+            (None, Some(sep)) => sep,
+            (None, None) => "_",
+        };
 
         // Define a prefix pattern to test and exclude from keys
         let prefix_pattern = self
             .prefix
             .as_ref()
-            .map(|prefix| format!("{}{}", prefix, group_separator).to_lowercase());
+            .map(|prefix| format!("{}{}", prefix, prefix_separator).to_lowercase());
 
         let collector = |(key, value): (String, String)| {
             // Treat empty environment variables as unset


Test patch:
diff --git a/tests/env.rs b/tests/env.rs
index edd8dff..90852e0 100644
--- a/tests/env.rs
+++ b/tests/env.rs
@@ -85,6 +85,19 @@ fn test_custom_separator_behavior() {
     env::remove_var("C.B.A");
 }
 
+#[test]
+fn test_custom_prefix_separator_behavior() {
+    env::set_var("C-B.A", "abc");
+
+    let environment = Environment::with_prefix("C")
+        .separator(".")
+        .prefix_separator("-");
+
+    assert!(environment.collect().unwrap().contains_key("b.a"));
+
+    env::remove_var("C-B.A");
+}
+
 #[test]
 fn test_parse_int() {
     // using a struct in an enum here to make serde use `deserialize_any`