Skip to content

Commit a8d72c6

Browse files
committed
Auto merge of #13956 - linyihai:missing-fields, r=weihanglo
Improve error description when deserializing partial field struct ### What does this PR try to resolve? Fixes #13688 ### How should we test and review this PR? one commit add test, one commit fixed and update the test. ### Additional information
2 parents 9f7a715 + e05d930 commit a8d72c6

File tree

4 files changed

+106
-12
lines changed

4 files changed

+106
-12
lines changed

src/cargo/util/context/de.rs

+23-9
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ macro_rules! deserialize_method {
3535
.ok_or_else(|| ConfigError::missing(&self.key))?;
3636
let Value { val, definition } = v;
3737
let res: Result<V::Value, ConfigError> = visitor.$visit(val);
38-
res.map_err(|e| e.with_key_context(&self.key, definition))
38+
res.map_err(|e| e.with_key_context(&self.key, Some(definition)))
3939
}
4040
};
4141
}
@@ -60,7 +60,7 @@ impl<'de, 'gctx> de::Deserializer<'de> for Deserializer<'gctx> {
6060
CV::Boolean(b, def) => (visitor.visit_bool(b), def),
6161
};
6262
let (res, def) = res;
63-
return res.map_err(|e| e.with_key_context(&self.key, def));
63+
return res.map_err(|e| e.with_key_context(&self.key, Some(def)));
6464
}
6565
Err(ConfigError::missing(&self.key))
6666
}
@@ -178,7 +178,7 @@ impl<'de, 'gctx> de::Deserializer<'de> for Deserializer<'gctx> {
178178
let Value { val, definition } = value;
179179
visitor
180180
.visit_enum(val.into_deserializer())
181-
.map_err(|e: ConfigError| e.with_key_context(&self.key, definition))
181+
.map_err(|e: ConfigError| e.with_key_context(&self.key, Some(definition)))
182182
}
183183

184184
// These aren't really supported, yet.
@@ -345,11 +345,25 @@ impl<'de, 'gctx> de::MapAccess<'de> for ConfigMapAccess<'gctx> {
345345
field.replace('-', "_").starts_with(&env_prefix)
346346
});
347347

348-
let result = seed.deserialize(Deserializer {
349-
gctx: self.de.gctx,
350-
key: self.de.key.clone(),
351-
env_prefix_ok,
352-
});
348+
let result = seed
349+
.deserialize(Deserializer {
350+
gctx: self.de.gctx,
351+
key: self.de.key.clone(),
352+
env_prefix_ok,
353+
})
354+
.map_err(|e| {
355+
if !e.is_missing_field() {
356+
return e;
357+
}
358+
e.with_key_context(
359+
&self.de.key,
360+
self.de
361+
.gctx
362+
.get_cv_with_env(&self.de.key)
363+
.ok()
364+
.and_then(|cv| cv.map(|cv| cv.get_definition().clone())),
365+
)
366+
});
353367
self.de.key.pop();
354368
result
355369
}
@@ -486,7 +500,7 @@ impl<'de, 'gctx> de::MapAccess<'de> for ValueDeserializer<'gctx> {
486500
if let Some(de) = &self.de {
487501
return seed
488502
.deserialize(de.clone())
489-
.map_err(|e| e.with_key_context(&de.key, self.definition.clone()));
503+
.map_err(|e| e.with_key_context(&de.key, Some(self.definition.clone())));
490504
} else {
491505
return seed
492506
.deserialize(self.str_value.as_ref().unwrap().clone().into_deserializer());

src/cargo/util/context/mod.rs

+34-2
Original file line numberDiff line numberDiff line change
@@ -2030,18 +2030,22 @@ impl ConfigError {
20302030
}
20312031
}
20322032

2033+
fn is_missing_field(&self) -> bool {
2034+
self.error.downcast_ref::<MissingField>().is_some()
2035+
}
2036+
20332037
fn missing(key: &ConfigKey) -> ConfigError {
20342038
ConfigError {
20352039
error: anyhow!("missing config key `{}`", key),
20362040
definition: None,
20372041
}
20382042
}
20392043

2040-
fn with_key_context(self, key: &ConfigKey, definition: Definition) -> ConfigError {
2044+
fn with_key_context(self, key: &ConfigKey, definition: Option<Definition>) -> ConfigError {
20412045
ConfigError {
20422046
error: anyhow::Error::from(self)
20432047
.context(format!("could not load config key `{}`", key)),
2044-
definition: Some(definition),
2048+
definition: definition,
20452049
}
20462050
}
20472051
}
@@ -2062,13 +2066,31 @@ impl fmt::Display for ConfigError {
20622066
}
20632067
}
20642068

2069+
#[derive(Debug)]
2070+
struct MissingField(String);
2071+
2072+
impl fmt::Display for MissingField {
2073+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2074+
write!(f, "missing field `{}`", self.0)
2075+
}
2076+
}
2077+
2078+
impl std::error::Error for MissingField {}
2079+
20652080
impl serde::de::Error for ConfigError {
20662081
fn custom<T: fmt::Display>(msg: T) -> Self {
20672082
ConfigError {
20682083
error: anyhow::Error::msg(msg.to_string()),
20692084
definition: None,
20702085
}
20712086
}
2087+
2088+
fn missing_field(field: &'static str) -> Self {
2089+
ConfigError {
2090+
error: anyhow::Error::new(MissingField(field.to_string())),
2091+
definition: None,
2092+
}
2093+
}
20722094
}
20732095

20742096
impl From<anyhow::Error> for ConfigError {
@@ -2111,6 +2133,16 @@ impl fmt::Debug for ConfigValue {
21112133
}
21122134

21132135
impl ConfigValue {
2136+
fn get_definition(&self) -> &Definition {
2137+
match self {
2138+
CV::Boolean(_, def)
2139+
| CV::Integer(_, def)
2140+
| CV::String(_, def)
2141+
| CV::List(_, def)
2142+
| CV::Table(_, def) => def,
2143+
}
2144+
}
2145+
21142146
fn from_toml(def: Definition, toml: toml::Value) -> CargoResult<ConfigValue> {
21152147
match toml {
21162148
toml::Value::String(val) => Ok(CV::String(val, def)),

tests/testsuite/config.rs

+45
Original file line numberDiff line numberDiff line change
@@ -1863,3 +1863,48 @@ fn trim_paths_parsing() {
18631863
let trim_paths: TomlTrimPaths = gctx.get("profile.dev.trim-paths").unwrap();
18641864
assert_eq!(trim_paths, expected, "failed to parse {val}");
18651865
}
1866+
1867+
#[cargo_test]
1868+
fn missing_fields() {
1869+
#[derive(Deserialize, Default, Debug)]
1870+
struct Foo {
1871+
bar: Bar,
1872+
}
1873+
1874+
#[derive(Deserialize, Default, Debug)]
1875+
struct Bar {
1876+
bax: bool,
1877+
baz: bool,
1878+
}
1879+
1880+
let gctx = GlobalContextBuilder::new()
1881+
.env("CARGO_FOO_BAR_BAZ", "true")
1882+
.build();
1883+
assert_error(
1884+
gctx.get::<Foo>("foo").unwrap_err(),
1885+
"\
1886+
could not load config key `foo.bar`
1887+
1888+
Caused by:
1889+
missing field `bax`",
1890+
);
1891+
let gctx: GlobalContext = GlobalContextBuilder::new()
1892+
.env("CARGO_FOO_BAR_BAZ", "true")
1893+
.env("CARGO_FOO_BAR_BAX", "true")
1894+
.build();
1895+
let foo = gctx.get::<Foo>("foo").unwrap();
1896+
assert_eq!(foo.bar.bax, true);
1897+
assert_eq!(foo.bar.baz, true);
1898+
1899+
let gctx: GlobalContext = GlobalContextBuilder::new()
1900+
.config_arg("foo.bar.baz=true")
1901+
.build();
1902+
assert_error(
1903+
gctx.get::<Foo>("foo").unwrap_err(),
1904+
"\
1905+
error in --config cli option: could not load config key `foo.bar`
1906+
1907+
Caused by:
1908+
missing field `bax`",
1909+
);
1910+
}

tests/testsuite/progress.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ fn bad_progress_config_missing_when() {
7070
.with_status(101)
7171
.with_stderr(
7272
"\
73-
error: missing field `when`
73+
error: error in [..]: could not load config key `term.progress`
74+
75+
Caused by:
76+
missing field `when`
7477
",
7578
)
7679
.run();

0 commit comments

Comments
 (0)