Skip to content

Commit cb53e1b

Browse files
committed
Improve TOML decoding error messages
Unfortunately while `#[serde(untagged)]` is precisely what we want in terms of semantics it leaves a little to be desired in terms of error messages. This commit updates to remove the usage of that attribute in favor of implementing `Deserialize` directly, which is quite simple in these few cases. Closes #3790
1 parent a226b3c commit cb53e1b

File tree

2 files changed

+194
-42
lines changed

2 files changed

+194
-42
lines changed

src/cargo/util/toml.rs

Lines changed: 127 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,42 @@ type TomlExampleTarget = TomlTarget;
191191
type TomlTestTarget = TomlTarget;
192192
type TomlBenchTarget = TomlTarget;
193193

194-
#[derive(Deserialize)]
195-
#[serde(untagged)]
196194
pub enum TomlDependency {
197195
Simple(String),
198196
Detailed(DetailedTomlDependency)
199197
}
200198

199+
impl de::Deserialize for TomlDependency {
200+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
201+
where D: de::Deserializer
202+
{
203+
struct TomlDependencyVisitor;
204+
205+
impl de::Visitor for TomlDependencyVisitor {
206+
type Value = TomlDependency;
207+
208+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
209+
formatter.write_str("a version string like \"0.9.8\" or a \
210+
detailed dependency like { version = \"0.9.8\" }")
211+
}
212+
213+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
214+
where E: de::Error
215+
{
216+
Ok(TomlDependency::Simple(s.to_owned()))
217+
}
218+
219+
fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
220+
where V: de::MapVisitor
221+
{
222+
let mvd = de::value::MapVisitorDeserializer::new(map);
223+
DetailedTomlDependency::deserialize(mvd).map(TomlDependency::Detailed)
224+
}
225+
}
226+
227+
deserializer.deserialize(TomlDependencyVisitor)
228+
}
229+
}
201230

202231
#[derive(Deserialize, Clone, Default)]
203232
pub struct DetailedTomlDependency {
@@ -288,13 +317,48 @@ impl de::Deserialize for TomlOptLevel {
288317
}
289318
}
290319

291-
#[derive(Deserialize, Clone)]
292-
#[serde(untagged)]
320+
#[derive(Clone)]
293321
pub enum U32OrBool {
294322
U32(u32),
295323
Bool(bool),
296324
}
297325

326+
impl de::Deserialize for U32OrBool {
327+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
328+
where D: de::Deserializer
329+
{
330+
struct Visitor;
331+
332+
impl de::Visitor for Visitor {
333+
type Value = U32OrBool;
334+
335+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
336+
formatter.write_str("a boolean or an integer")
337+
}
338+
339+
fn visit_i64<E>(self, u: i64) -> Result<Self::Value, E>
340+
where E: de::Error,
341+
{
342+
Ok(U32OrBool::U32(u as u32))
343+
}
344+
345+
fn visit_u64<E>(self, u: u64) -> Result<Self::Value, E>
346+
where E: de::Error,
347+
{
348+
Ok(U32OrBool::U32(u as u32))
349+
}
350+
351+
fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
352+
where E: de::Error,
353+
{
354+
Ok(U32OrBool::Bool(b))
355+
}
356+
}
357+
358+
deserializer.deserialize(Visitor)
359+
}
360+
}
361+
298362
#[derive(Deserialize, Clone, Default)]
299363
pub struct TomlProfile {
300364
#[serde(rename = "opt-level")]
@@ -309,13 +373,42 @@ pub struct TomlProfile {
309373
panic: Option<String>,
310374
}
311375

312-
#[derive(Deserialize, Clone, Debug)]
313-
#[serde(untagged)]
376+
#[derive(Clone, Debug)]
314377
pub enum StringOrBool {
315378
String(String),
316379
Bool(bool),
317380
}
318381

382+
impl de::Deserialize for StringOrBool {
383+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
384+
where D: de::Deserializer
385+
{
386+
struct Visitor;
387+
388+
impl de::Visitor for Visitor {
389+
type Value = StringOrBool;
390+
391+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
392+
formatter.write_str("a boolean or a string")
393+
}
394+
395+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
396+
where E: de::Error,
397+
{
398+
Ok(StringOrBool::String(s.to_string()))
399+
}
400+
401+
fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
402+
where E: de::Error,
403+
{
404+
Ok(StringOrBool::Bool(b))
405+
}
406+
}
407+
408+
deserializer.deserialize(Visitor)
409+
}
410+
}
411+
319412
#[derive(Deserialize)]
320413
pub struct TomlProject {
321414
name: String,
@@ -405,7 +498,7 @@ fn inferred_lib_target(name: &str, layout: &Layout) -> Option<TomlTarget> {
405498
layout.lib.as_ref().map(|lib| {
406499
TomlTarget {
407500
name: Some(name.to_string()),
408-
path: Some(PathValue::Path(lib.clone())),
501+
path: Some(PathValue(lib.clone())),
409502
.. TomlTarget::new()
410503
}
411504
})
@@ -423,7 +516,7 @@ fn inferred_bin_targets(name: &str, layout: &Layout) -> Vec<TomlTarget> {
423516
name.map(|name| {
424517
TomlTarget {
425518
name: Some(name),
426-
path: Some(PathValue::Path(bin.clone())),
519+
path: Some(PathValue(bin.clone())),
427520
.. TomlTarget::new()
428521
}
429522
})
@@ -435,7 +528,7 @@ fn inferred_example_targets(layout: &Layout) -> Vec<TomlTarget> {
435528
ex.file_stem().and_then(|s| s.to_str()).map(|name| {
436529
TomlTarget {
437530
name: Some(name.to_string()),
438-
path: Some(PathValue::Path(ex.clone())),
531+
path: Some(PathValue(ex.clone())),
439532
.. TomlTarget::new()
440533
}
441534
})
@@ -447,7 +540,7 @@ fn inferred_test_targets(layout: &Layout) -> Vec<TomlTarget> {
447540
ex.file_stem().and_then(|s| s.to_str()).map(|name| {
448541
TomlTarget {
449542
name: Some(name.to_string()),
450-
path: Some(PathValue::Path(ex.clone())),
543+
path: Some(PathValue(ex.clone())),
451544
.. TomlTarget::new()
452545
}
453546
})
@@ -459,7 +552,7 @@ fn inferred_bench_targets(layout: &Layout) -> Vec<TomlTarget> {
459552
ex.file_stem().and_then(|s| s.to_str()).map(|name| {
460553
TomlTarget {
461554
name: Some(name.to_string()),
462-
path: Some(PathValue::Path(ex.clone())),
555+
path: Some(PathValue(ex.clone())),
463556
.. TomlTarget::new()
464557
}
465558
})
@@ -498,7 +591,7 @@ impl TomlManifest {
498591
TomlTarget {
499592
name: lib.name.clone().or(Some(project.name.clone())),
500593
path: lib.path.clone().or_else(
501-
|| layout.lib.as_ref().map(|p| PathValue::Path(p.clone()))
594+
|| layout.lib.as_ref().map(|p| PathValue(p.clone()))
502595
),
503596
..lib.clone()
504597
}
@@ -995,11 +1088,15 @@ struct TomlTarget {
9951088
required_features: Option<Vec<String>>,
9961089
}
9971090

998-
#[derive(Deserialize, Clone)]
999-
#[serde(untagged)]
1000-
enum PathValue {
1001-
String(String),
1002-
Path(PathBuf),
1091+
#[derive(Clone)]
1092+
struct PathValue(PathBuf);
1093+
1094+
impl de::Deserialize for PathValue {
1095+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1096+
where D: de::Deserializer
1097+
{
1098+
Ok(PathValue(String::deserialize(deserializer)?.into()))
1099+
}
10031100
}
10041101

10051102
/// Corresponds to a `target` entry, but `TomlTarget` is already used.
@@ -1118,21 +1215,9 @@ impl TomlTarget {
11181215
}
11191216
}
11201217

1121-
impl PathValue {
1122-
fn to_path(&self) -> PathBuf {
1123-
match *self {
1124-
PathValue::String(ref s) => PathBuf::from(s),
1125-
PathValue::Path(ref p) => p.clone(),
1126-
}
1127-
}
1128-
}
1129-
11301218
impl fmt::Debug for PathValue {
11311219
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1132-
match *self {
1133-
PathValue::String(ref s) => s.fmt(f),
1134-
PathValue::Path(ref p) => p.display().fmt(f),
1135-
}
1220+
self.0.fmt(f)
11361221
}
11371222
}
11381223

@@ -1159,7 +1244,7 @@ fn normalize(package_root: &Path,
11591244

11601245
let lib_target = |dst: &mut Vec<Target>, l: &TomlLibTarget| {
11611246
let path = l.path.clone().unwrap_or_else(
1162-
|| PathValue::Path(Path::new("src").join(&format!("{}.rs", l.name())))
1247+
|| PathValue(Path::new("src").join(&format!("{}.rs", l.name())))
11631248
);
11641249
let crate_types = l.crate_type.as_ref().or(l.crate_type2.as_ref());
11651250
let crate_types = match crate_types {
@@ -1172,7 +1257,7 @@ fn normalize(package_root: &Path,
11721257
};
11731258

11741259
let mut target = Target::lib_target(&l.name(), crate_types,
1175-
package_root.join(path.to_path()));
1260+
package_root.join(&path.0));
11761261
configure(l, &mut target);
11771262
dst.push(target);
11781263
};
@@ -1181,14 +1266,14 @@ fn normalize(package_root: &Path,
11811266
default: &mut FnMut(&TomlBinTarget) -> PathBuf| {
11821267
for bin in bins.iter() {
11831268
let path = bin.path.clone().unwrap_or_else(|| {
1184-
let default_bin_path = PathValue::Path(default(bin));
1185-
if package_root.join(default_bin_path.to_path()).exists() {
1269+
let default_bin_path = PathValue(default(bin));
1270+
if package_root.join(&default_bin_path.0).exists() {
11861271
default_bin_path // inferred from bin's name
11871272
} else {
1188-
PathValue::Path(Path::new("src").join("main.rs"))
1273+
PathValue(Path::new("src").join("main.rs"))
11891274
}
11901275
});
1191-
let mut target = Target::bin_target(&bin.name(), package_root.join(path.to_path()),
1276+
let mut target = Target::bin_target(&bin.name(), package_root.join(&path.0),
11921277
bin.required_features.clone());
11931278
configure(bin, &mut target);
11941279
dst.push(target);
@@ -1207,7 +1292,7 @@ fn normalize(package_root: &Path,
12071292
default: &mut FnMut(&TomlExampleTarget) -> PathBuf| {
12081293
for ex in examples.iter() {
12091294
let path = ex.path.clone().unwrap_or_else(|| {
1210-
PathValue::Path(default(ex))
1295+
PathValue(default(ex))
12111296
});
12121297

12131298
let crate_types = ex.crate_type.as_ref().or(ex.crate_type2.as_ref());
@@ -1219,7 +1304,7 @@ fn normalize(package_root: &Path,
12191304
let mut target = Target::example_target(
12201305
&ex.name(),
12211306
crate_types,
1222-
package_root.join(path.to_path()),
1307+
package_root.join(&path.0),
12231308
ex.required_features.clone()
12241309
);
12251310
configure(ex, &mut target);
@@ -1232,10 +1317,10 @@ fn normalize(package_root: &Path,
12321317
default: &mut FnMut(&TomlTestTarget) -> PathBuf| {
12331318
for test in tests.iter() {
12341319
let path = test.path.clone().unwrap_or_else(|| {
1235-
PathValue::Path(default(test))
1320+
PathValue(default(test))
12361321
});
12371322

1238-
let mut target = Target::test_target(&test.name(), package_root.join(path.to_path()),
1323+
let mut target = Target::test_target(&test.name(), package_root.join(&path.0),
12391324
test.required_features.clone());
12401325
configure(test, &mut target);
12411326
dst.push(target);
@@ -1247,10 +1332,10 @@ fn normalize(package_root: &Path,
12471332
default: &mut FnMut(&TomlBenchTarget) -> PathBuf| {
12481333
for bench in benches.iter() {
12491334
let path = bench.path.clone().unwrap_or_else(|| {
1250-
PathValue::Path(default(bench))
1335+
PathValue(default(bench))
12511336
});
12521337

1253-
let mut target = Target::bench_target(&bench.name(), package_root.join(path.to_path()),
1338+
let mut target = Target::bench_target(&bench.name(), package_root.join(&path.0),
12541339
bench.required_features.clone());
12551340
configure(bench, &mut target);
12561341
dst.push(target);

tests/bad-config.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,3 +947,70 @@ fn bad_source_config7() {
947947
error: more than one source URL specified for `source.foo`
948948
"));
949949
}
950+
951+
#[test]
952+
fn bad_dependency() {
953+
let p = project("foo")
954+
.file("Cargo.toml", r#"
955+
[package]
956+
name = "foo"
957+
version = "0.0.0"
958+
authors = []
959+
960+
[dependencies]
961+
bar = 3
962+
"#)
963+
.file("src/lib.rs", "");
964+
965+
assert_that(p.cargo_process("build"),
966+
execs().with_status(101).with_stderr("\
967+
error: failed to parse manifest at `[..]`
968+
969+
Caused by:
970+
invalid type: integer `3`, expected a version string like [..]
971+
"));
972+
}
973+
974+
#[test]
975+
fn bad_debuginfo() {
976+
let p = project("foo")
977+
.file("Cargo.toml", r#"
978+
[package]
979+
name = "foo"
980+
version = "0.0.0"
981+
authors = []
982+
983+
[profile.dev]
984+
debug = 'a'
985+
"#)
986+
.file("src/lib.rs", "");
987+
988+
assert_that(p.cargo_process("build"),
989+
execs().with_status(101).with_stderr("\
990+
error: failed to parse manifest at `[..]`
991+
992+
Caused by:
993+
invalid type: string \"a\", expected a boolean or an integer for [..]
994+
"));
995+
}
996+
997+
#[test]
998+
fn bad_opt_level() {
999+
let p = project("foo")
1000+
.file("Cargo.toml", r#"
1001+
[package]
1002+
name = "foo"
1003+
version = "0.0.0"
1004+
authors = []
1005+
build = 3
1006+
"#)
1007+
.file("src/lib.rs", "");
1008+
1009+
assert_that(p.cargo_process("build"),
1010+
execs().with_status(101).with_stderr("\
1011+
error: failed to parse manifest at `[..]`
1012+
1013+
Caused by:
1014+
invalid type: integer `3`, expected a boolean or a string for key [..]
1015+
"));
1016+
}

0 commit comments

Comments
 (0)