diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a8899f6..e0c8d2bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] - ReleaseDate +### Fixes + +- `Environment::convert_case` now applies the case conversion to each nested key segment + ## [0.15.22] - 2026-03-17 ### Documentation diff --git a/src/env.rs b/src/env.rs index c647da30..b78c7e53 100644 --- a/src/env.rs +++ b/src/env.rs @@ -296,7 +296,11 @@ impl Source for Environment { #[cfg(feature = "convert-case")] if let Some(convert_case) = convert_case { - key = key.to_case(*convert_case); + key = key + .split('.') + .map(|segment| segment.to_case(*convert_case)) + .collect::>() + .join("."); } let value = if self.try_parsing { diff --git a/tests/testsuite/env.rs b/tests/testsuite/env.rs index 58d3d575..e83a0947 100644 --- a/tests/testsuite/env.rs +++ b/tests/testsuite/env.rs @@ -574,6 +574,38 @@ fn test_parse_nested_kebab() { ); } +#[test] +#[cfg(feature = "convert-case")] +fn test_parse_nested_upper_camel() { + use config::Case; + + #[derive(Deserialize, Debug)] + struct TestConfig { + #[serde(rename = "Otel")] + otel: Inner, + } + + #[derive(Deserialize, Debug)] + struct Inner { + #[serde(rename = "Endpoint")] + endpoint: String, + } + + temp_env::with_var("CONFIG_OTEL__ENDPOINT", Some("from env"), || { + let environment = Environment::default() + .prefix("CONFIG") + .prefix_separator("_") + .separator("__") + .convert_case(Case::UpperCamel); + + let config = Config::builder().add_source(environment).build().unwrap(); + + let config: TestConfig = config.try_deserialize().unwrap(); + + assert_eq!(config.otel.endpoint, "from env"); + }); +} + #[test] fn test_parse_string() { // using a struct in an enum here to make serde use `deserialize_any`