summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrahm Lower <bplower@gmail.com>2022-11-20 09:27:48 -0800
committerGitHub <noreply@github.com>2022-11-20 18:27:48 +0100
commit1b03ef21f34fc4acf890b01cfca3d07158ef5c46 (patch)
treef3e7e9abfcf1dc87721093b20ce05f9af7dcda99
parentc2d3845dbbb4fbf4fcb034f952be668929246bd0 (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.rs30
-rw-r--r--src/serde_utils.rs34
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]