diff options
author | Brahm Lower <bplower@gmail.com> | 2022-11-20 09:27:48 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-20 18:27:48 +0100 |
commit | 1b03ef21f34fc4acf890b01cfca3d07158ef5c46 (patch) | |
tree | f3e7e9abfcf1dc87721093b20ce05f9af7dcda99 | |
parent | c2d3845dbbb4fbf4fcb034f952be668929246bd0 (diff) |
fix(config): unrecognized config properties don't cause config error (#4547)
* Fix #4481, config does not error when unrecognized properties are present
* cleanup: use stuct update syntax to improve readability
from review feedback
Co-authored-by: David Knaack <davidkna@users.noreply.github.com>
* cleanup: renamed ValueDeserializer func w/ better name
* cleanup: added test to cover unknown key retry condition
Co-authored-by: David Knaack <davidkna@users.noreply.github.com>
-rw-r--r-- | src/config.rs | 30 | ||||
-rw-r--r-- | src/serde_utils.rs | 34 |
2 files changed, 60 insertions, 4 deletions
diff --git a/src/config.rs b/src/config.rs index 35cb86563..e0aa016ac 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,7 +48,17 @@ impl<'a, T: Deserialize<'a> + Default> ModuleConfig<'a, ValueError> for T { /// Create `ValueDeserializer` wrapper and use it to call `Deserialize::deserialize` on it. fn from_config(config: &'a Value) -> Result<Self, ValueError> { let deserializer = ValueDeserializer::new(config); - T::deserialize(deserializer) + T::deserialize(deserializer).or_else(|err| { + // If the error is an unrecognized key, print a warning and run + // deserialize ignoring that error. Otherwise, just return the error + if err.to_string().contains("Unknown key") { + log::warn!("{}", err); + let deserializer2 = ValueDeserializer::new(config).with_allow_unknown_keys(); + T::deserialize(deserializer2) + } else { + Err(err) + } + }) } } @@ -583,6 +593,24 @@ mod tests { } #[test] + fn test_load_unknown_key_config() { + #[derive(Clone, Default, Deserialize)] + #[serde(default)] + struct TestConfig<'a> { + pub foo: &'a str, + } + + let config = toml::toml! { + foo = "test" + bar = "ignore me" + }; + let rust_config = TestConfig::from_config(&config); + + assert!(rust_config.is_ok()); + assert_eq!(rust_config.unwrap().foo, "test"); + } + + #[test] fn test_from_string() { let config = Value::String(String::from("S")); assert_eq!(<&str>::from_config(&config).unwrap(), "S"); diff --git a/src/serde_utils.rs b/src/serde_utils.rs index c1c383848..b47c02cd5 100644 --- a/src/serde_utils.rs +++ b/src/serde_utils.rs @@ -13,6 +13,7 @@ pub struct ValueDeserializer<'de> { value: &'de Value, info: Option<StructInfo>, current_key: Option<&'de str>, + error_on_ignored: bool, } /// When deserializing a struct, this struct stores information about the struct. @@ -28,14 +29,28 @@ impl<'de> ValueDeserializer<'de> { value, info: None, current_key: None, + error_on_ignored: true, } } - fn with_info(value: &'de Value, info: Option<StructInfo>, current_key: &'de str) -> Self { + fn with_info( + value: &'de Value, + info: Option<StructInfo>, + current_key: &'de str, + ignored: bool, + ) -> Self { ValueDeserializer { value, info, current_key: Some(current_key), + error_on_ignored: ignored, + } + } + + pub fn with_allow_unknown_keys(self) -> Self { + ValueDeserializer { + error_on_ignored: false, + ..self } } } @@ -83,7 +98,12 @@ impl<'de> Deserializer<'de> for ValueDeserializer<'de> { let map = MapDeserializer::new(t.iter().map(|(k, v)| { ( k.as_str(), - ValueDeserializer::with_info(v, self.info, k.as_str()), + ValueDeserializer::with_info( + v, + self.info, + k.as_str(), + self.error_on_ignored, + ), ) })); map.deserialize_map(visitor) @@ -131,6 +151,10 @@ impl<'de> Deserializer<'de> for ValueDeserializer<'de> { return visitor.visit_none(); } + if !self.error_on_ignored { + return visitor.visit_none(); + } + let did_you_mean = match (self.current_key, self.info) { (Some(key), Some(StructInfo { fields, .. })) => fields .iter() @@ -322,13 +346,17 @@ mod test { let value = toml::toml! { unknown_key = "foo" }; - let deserializer = ValueDeserializer::new(&value); + let deserializer = ValueDeserializer::new(&value); let result = StarshipRootConfig::deserialize(deserializer).unwrap_err(); assert_eq!( format!("{result}"), "Error in 'StarshipRoot' at 'unknown_key': Unknown key" ); + + let deserializer2 = ValueDeserializer::new(&value).with_allow_unknown_keys(); + let result = StarshipRootConfig::deserialize(deserializer2); + assert!(result.is_ok()); } #[test] |