diff --git a/src/path/mod.rs b/src/path/mod.rs index 333a4d57..2fa5af2b 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -150,12 +150,16 @@ impl Expression { let parent = self.get_mut_forcibly(root); match value.kind { ValueKind::Table(ref incoming_map) => { + // If the parent is nil, treat it as an empty table + if matches!(parent.kind, ValueKind::Nil) { + *parent = Map::::new().into(); + } + // Continue the deep merge for (key, val) in incoming_map { Self::root(key.clone()).set(parent, val.clone()); } } - _ => { *parent = value; } diff --git a/tests/testsuite/file_toml.rs b/tests/testsuite/file_toml.rs index 6c3980c5..9e89fb0c 100644 --- a/tests/testsuite/file_toml.rs +++ b/tests/testsuite/file_toml.rs @@ -1,5 +1,7 @@ #![cfg(feature = "toml")] +use std::collections::BTreeMap; + use chrono::{DateTime, TimeZone, Utc}; use float_cmp::ApproxEqUlps; use serde_derive::Deserialize; @@ -422,6 +424,60 @@ bar = "bar is a lowercase param" ); } +#[test] +fn test_override_empty_tables() { + #[derive(Debug, Deserialize)] + struct Settings { + profile: BTreeMap, + } + + #[derive(Debug, Default, Deserialize)] + struct Profile { + name: Option, + } + + // Test a few scenarios with empty tables: + // * foo: empty table -> table with k/v + // * bar: table with k/v -> empty table + // * baz: empty table relying on Default impl + let cfg = Config::builder() + .add_source(File::from_str( + r#" + [profile.foo] +"#, + FileFormat::Toml, + )) + .add_source(File::from_str( + r#" + [profile.foo] + name = "foo" +"#, + FileFormat::Toml, + )) + .add_source(File::from_str( + r#" + [profile.bar] + name = "bar" +"#, + FileFormat::Toml, + )) + .add_source(File::from_str( + r#" + [profile.bar] + [profile.baz] +"#, + FileFormat::Toml, + )) + .build() + .unwrap(); + + let settings: Settings = cfg.try_deserialize().unwrap(); + assert_eq!(settings.profile.len(), 3); + assert_eq!(settings.profile["foo"].name.as_deref(), Some("foo")); + assert_eq!(settings.profile["bar"].name.as_deref(), Some("bar")); + assert_eq!(settings.profile["baz"].name.as_deref(), None); +} + #[test] fn toml() { let s = Config::builder()