fix: Add more context when 'missing feild'

This commit is contained in:
Lin Yihai 2024-05-23 15:29:38 +08:00
parent 48742e009c
commit e05d930e16
4 changed files with 77 additions and 14 deletions

View file

@ -35,7 +35,7 @@ macro_rules! deserialize_method {
.ok_or_else(|| ConfigError::missing(&self.key))?;
let Value { val, definition } = v;
let res: Result<V::Value, ConfigError> = visitor.$visit(val);
res.map_err(|e| e.with_key_context(&self.key, definition))
res.map_err(|e| e.with_key_context(&self.key, Some(definition)))
}
};
}
@ -60,7 +60,7 @@ impl<'de, 'gctx> de::Deserializer<'de> for Deserializer<'gctx> {
CV::Boolean(b, def) => (visitor.visit_bool(b), def),
};
let (res, def) = res;
return res.map_err(|e| e.with_key_context(&self.key, def));
return res.map_err(|e| e.with_key_context(&self.key, Some(def)));
}
Err(ConfigError::missing(&self.key))
}
@ -178,7 +178,7 @@ impl<'de, 'gctx> de::Deserializer<'de> for Deserializer<'gctx> {
let Value { val, definition } = value;
visitor
.visit_enum(val.into_deserializer())
.map_err(|e: ConfigError| e.with_key_context(&self.key, definition))
.map_err(|e: ConfigError| e.with_key_context(&self.key, Some(definition)))
}
// These aren't really supported, yet.
@ -345,11 +345,25 @@ impl<'de, 'gctx> de::MapAccess<'de> for ConfigMapAccess<'gctx> {
field.replace('-', "_").starts_with(&env_prefix)
});
let result = seed.deserialize(Deserializer {
gctx: self.de.gctx,
key: self.de.key.clone(),
env_prefix_ok,
});
let result = seed
.deserialize(Deserializer {
gctx: self.de.gctx,
key: self.de.key.clone(),
env_prefix_ok,
})
.map_err(|e| {
if !e.is_missing_field() {
return e;
}
e.with_key_context(
&self.de.key,
self.de
.gctx
.get_cv_with_env(&self.de.key)
.ok()
.and_then(|cv| cv.map(|cv| cv.get_definition().clone())),
)
});
self.de.key.pop();
result
}
@ -486,7 +500,7 @@ impl<'de, 'gctx> de::MapAccess<'de> for ValueDeserializer<'gctx> {
if let Some(de) = &self.de {
return seed
.deserialize(de.clone())
.map_err(|e| e.with_key_context(&de.key, self.definition.clone()));
.map_err(|e| e.with_key_context(&de.key, Some(self.definition.clone())));
} else {
return seed
.deserialize(self.str_value.as_ref().unwrap().clone().into_deserializer());

View file

@ -2030,6 +2030,10 @@ impl ConfigError {
}
}
fn is_missing_field(&self) -> bool {
self.error.downcast_ref::<MissingField>().is_some()
}
fn missing(key: &ConfigKey) -> ConfigError {
ConfigError {
error: anyhow!("missing config key `{}`", key),
@ -2037,11 +2041,11 @@ impl ConfigError {
}
}
fn with_key_context(self, key: &ConfigKey, definition: Definition) -> ConfigError {
fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
ConfigError {
error: anyhow::Error::from(self)
.context(format!("could not load config key `{}`", key)),
definition: Some(definition),
definition: definition,
}
}
}
@ -2062,6 +2066,17 @@ impl fmt::Display for ConfigError {
}
}
#[derive(Debug)]
struct MissingField(String);
impl fmt::Display for MissingField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "missing field `{}`", self.0)
}
}
impl std::error::Error for MissingField {}
impl serde::de::Error for ConfigError {
fn custom<T: fmt::Display>(msg: T) -> Self {
ConfigError {
@ -2069,6 +2084,13 @@ impl serde::de::Error for ConfigError {
definition: None,
}
}
fn missing_field(field: &'static str) -> Self {
ConfigError {
error: anyhow::Error::new(MissingField(field.to_string())),
definition: None,
}
}
}
impl From<anyhow::Error> for ConfigError {
@ -2111,6 +2133,16 @@ impl fmt::Debug for ConfigValue {
}
impl ConfigValue {
fn get_definition(&self) -> &Definition {
match self {
CV::Boolean(_, def)
| CV::Integer(_, def)
| CV::String(_, def)
| CV::List(_, def)
| CV::Table(_, def) => def,
}
}
fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
match toml {
toml::Value::String(val) => Ok(CV::String(val, def)),

View file

@ -1880,7 +1880,14 @@ fn missing_fields() {
let gctx = GlobalContextBuilder::new()
.env("CARGO_FOO_BAR_BAZ", "true")
.build();
assert_error(gctx.get::<Foo>("foo").unwrap_err(), "missing field `bax`");
assert_error(
gctx.get::<Foo>("foo").unwrap_err(),
"\
could not load config key `foo.bar`
Caused by:
missing field `bax`",
);
let gctx: GlobalContext = GlobalContextBuilder::new()
.env("CARGO_FOO_BAR_BAZ", "true")
.env("CARGO_FOO_BAR_BAX", "true")
@ -1892,5 +1899,12 @@ fn missing_fields() {
let gctx: GlobalContext = GlobalContextBuilder::new()
.config_arg("foo.bar.baz=true")
.build();
assert_error(gctx.get::<Foo>("foo").unwrap_err(), "missing field `bax`");
assert_error(
gctx.get::<Foo>("foo").unwrap_err(),
"\
error in --config cli option: could not load config key `foo.bar`
Caused by:
missing field `bax`",
);
}

View file

@ -70,7 +70,10 @@ fn bad_progress_config_missing_when() {
.with_status(101)
.with_stderr(
"\
error: missing field `when`
error: error in [..]: could not load config key `term.progress`
Caused by:
missing field `when`
",
)
.run();