Repository: toml-rs/toml (language: rust)

Issue:
Missing data in cargo test failures
Unsure if this is a regression compared to toml-rs or some other failure

Examples:
```
---- bad_config::bad_debuginfo stdout ----                                                                                                         
running `/home/epage/src/personal/cargo/target/debug/cargo build`                                                                                  
thread 'bad_config::bad_debuginfo' panicked at '                                                                                                   
test failed running `/home/epage/src/personal/cargo/target/debug/cargo build`                                                                      
error: stderr did not match:                                                                                                                       
1   1     error: failed to parse manifest at `[..]`                                                                                                
2   2                                                                                                                                              
3   3     Caused by:                                                                                                                               
4        -  expected a boolean or an integer for [..]                                                                                              
    4    +  expected a boolean or an integer                                                                                                       

---- bad_config::bad_cargo_lock stdout ----                                                                                                        
running `/home/epage/src/personal/cargo/target/debug/cargo build -v`                                                                               
thread 'bad_config::bad_cargo_lock' panicked at '                                                                                                  
test failed running `/home/epage/src/personal/cargo/target/debug/cargo build -v`                                                                   
error: stderr did not match:                                                                                                                       
1   1     error: failed to parse lock file at: [..]Cargo.lock                                                                                      
2   2                                                                                                                                              
3   3     Caused by:                                                                                                                               
4        -  missing field `name` for key `package`                                                                                                 
    4    +  missing field `name`                                                                                                                   


---- bad_config::bad_source_in_cargo_lock stdout ----                                                                                              
running `/home/epage/src/personal/cargo/target/debug/cargo build --verbose`                                                                        
thread 'bad_config::bad_source_in_cargo_lock' panicked at '                                                                                        
test failed running `/home/epage/src/personal/cargo/target/debug/cargo build --verbose`                                                            
error: stderr did not match:                                                                                                                       
1   1     error: failed to parse lock file at: [..]                                                                                                
2   2                                                                                                                                              
3   3     Caused by:                                                                                                                               
4        -  invalid source `You shall not parse` for key `package.source`                                                                          
    4    +  invalid source `You shall not parse`                                                                                                   

---- build::cargo_compile_with_invalid_version stdout ----                                                                                         
running `/home/epage/src/personal/cargo/target/debug/cargo build`                                                                                  
thread 'build::cargo_compile_with_invalid_version' panicked at '                                                                                   
test failed running `/home/epage/src/personal/cargo/target/debug/cargo build`                                                                      
error: stderr did not match:                                             
1   1     error: failed to parse manifest at `[..]`                      
2   2                                                                    
3   3     Caused by:                                                     
4        -  unexpected end of input while parsing minor version number for key `package.version`                                                   
    4    +  unexpected end of input while parsing minor version number                                                                             
```


Discussion / hints:

Fix patch:
diff --git a/src/de/inline_table.rs b/src/de/inline_table.rs
index 42e07608..6526e79d 100644
--- a/src/de/inline_table.rs
+++ b/src/de/inline_table.rs
@@ -84,7 +84,7 @@ impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::InlineTa
 
 pub(crate) struct InlineTableMapAccess {
     iter: indexmap::map::IntoIter<crate::InternalString, crate::table::TableKeyValue>,
-    value: Option<crate::Item>,
+    value: Option<(crate::InternalString, crate::Item)>,
 }
 
 impl InlineTableMapAccess {
@@ -105,8 +105,9 @@ impl<'de> serde::de::MapAccess<'de> for InlineTableMapAccess {
     {
         match self.iter.next() {
             Some((k, v)) => {
-                self.value = Some(v.value);
-                seed.deserialize(k.into_deserializer()).map(Some)
+                let ret = seed.deserialize(k.into_deserializer()).map(Some);
+                self.value = Some((k, v.value));
+                ret
             }
             None => Ok(None),
         }
@@ -117,7 +118,12 @@ impl<'de> serde::de::MapAccess<'de> for InlineTableMapAccess {
         V: serde::de::DeserializeSeed<'de>,
     {
         match self.value.take() {
-            Some(v) => seed.deserialize(crate::de::ItemDeserializer::new(v)),
+            Some((k, v)) => seed
+                .deserialize(crate::de::ItemDeserializer::new(v))
+                .map_err(|mut err| {
+                    err.parent_key(k);
+                    err
+                }),
             None => {
                 panic!("no more values in next_value_seed, internal error in ValueDeserializer")
             }
diff --git a/src/de/mod.rs b/src/de/mod.rs
index 219c5182..80b8b305 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -21,7 +21,13 @@ use table_enum::*;
 /// Errors that can occur when deserializing a type.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Error {
+    inner: Box<ErrorInner>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+struct ErrorInner {
     message: String,
+    reverse_key: Vec<crate::InternalString>,
 }
 
 impl Error {
@@ -30,9 +36,16 @@ impl Error {
         T: std::fmt::Display,
     {
         Error {
-            message: msg.to_string(),
+            inner: Box::new(ErrorInner {
+                message: msg.to_string(),
+                reverse_key: Default::default(),
+            }),
         }
     }
+
+    pub(crate) fn parent_key(&mut self, key: crate::InternalString) {
+        self.inner.reverse_key.push(key);
+    }
 }
 
 impl serde::de::Error for Error {
@@ -40,15 +53,26 @@ impl serde::de::Error for Error {
     where
         T: std::fmt::Display,
     {
-        Error {
-            message: msg.to_string(),
-        }
+        Error::custom(msg)
     }
 }
 
 impl std::fmt::Display for Error {
-    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
-        self.message.fmt(formatter)
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        self.inner.message.fmt(f)?;
+
+        if !self.inner.reverse_key.is_empty() {
+            write!(f, " for key `")?;
+            for (i, k) in self.inner.reverse_key.iter().rev().enumerate() {
+                if i > 0 {
+                    write!(f, ".")?;
+                }
+                write!(f, "{}", k)?;
+            }
+            write!(f, "`")?;
+        }
+
+        Ok(())
     }
 }
 
diff --git a/src/de/table.rs b/src/de/table.rs
index 05d19580..c8659446 100644
--- a/src/de/table.rs
+++ b/src/de/table.rs
@@ -84,7 +84,7 @@ impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::Table {
 
 pub(crate) struct TableMapAccess {
     iter: indexmap::map::IntoIter<crate::InternalString, crate::table::TableKeyValue>,
-    value: Option<crate::Item>,
+    value: Option<(crate::InternalString, crate::Item)>,
 }
 
 impl TableMapAccess {
@@ -105,8 +105,9 @@ impl<'de> serde::de::MapAccess<'de> for TableMapAccess {
     {
         match self.iter.next() {
             Some((k, v)) => {
-                self.value = Some(v.value);
-                seed.deserialize(k.into_deserializer()).map(Some)
+                let ret = seed.deserialize(k.into_deserializer()).map(Some);
+                self.value = Some((k, v.value));
+                ret
             }
             None => Ok(None),
         }
@@ -117,7 +118,12 @@ impl<'de> serde::de::MapAccess<'de> for TableMapAccess {
         V: serde::de::DeserializeSeed<'de>,
     {
         match self.value.take() {
-            Some(v) => seed.deserialize(crate::de::ItemDeserializer::new(v)),
+            Some((k, v)) => seed
+                .deserialize(crate::de::ItemDeserializer::new(v))
+                .map_err(|mut err| {
+                    err.parent_key(k);
+                    err
+                }),
             None => {
                 panic!("no more values in next_value_seed, internal error in ItemDeserializer")
             }


Test patch:
diff --git a/tests/enum_external_deserialize.rs b/tests/enum_external_deserialize.rs
index 17e18c4d..5321ffe4 100644
--- a/tests/enum_external_deserialize.rs
+++ b/tests/enum_external_deserialize.rs
@@ -32,7 +32,7 @@ fn invalid_variant_returns_error_with_good_message_string() {
 
     assert_eq!(
         error.to_string(),
-        "unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`"
+        "unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` for key `val`"
     );
 }
 
@@ -41,7 +41,7 @@ fn invalid_variant_returns_error_with_good_message_inline_table() {
     let error = toml::from_str::<Val>("val = { NonExistent = {} }").unwrap_err();
     assert_eq!(
         error.to_string(),
-        "unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct`"
+        "unknown variant `NonExistent`, expected one of `Plain`, `Tuple`, `NewType`, `Struct` for key `val`"
     );
 }
 
@@ -49,7 +49,7 @@ fn invalid_variant_returns_error_with_good_message_inline_table() {
 fn extra_field_returns_expected_empty_table_error() {
     let error = toml::from_str::<Val>("val = { Plain = { extra_field = 404 } }").unwrap_err();
 
-    assert_eq!(error.to_string(), "expected empty table");
+    assert_eq!(error.to_string(), "expected empty table for key `val`");
 }
 
 #[test]
@@ -60,7 +60,7 @@ fn extra_field_returns_expected_empty_table_error_struct_variant() {
 
     assert_eq!(
         error.to_string(),
-        r#"unexpected keys in table: extra_0, extra_1, available keys: value"#
+        r#"unexpected keys in table: extra_0, extra_1, available keys: value for key `val`"#
     );
 }
 
diff --git a/tests/serde.rs b/tests/serde.rs
index 3f9a438c..d9557792 100644
--- a/tests/serde.rs
+++ b/tests/serde.rs
@@ -211,8 +211,8 @@ fn type_errors() {
         Table(map! {
             bar: Value::String("a".to_string())
         }),
-        "invalid type: string \"a\", expected isize",
-        "invalid type: string \"a\", expected isize"
+        "invalid type: string \"a\", expected isize for key `bar`",
+        "invalid type: string \"a\", expected isize for key `bar`"
     }
 
     #[derive(Deserialize)]
@@ -228,8 +228,8 @@ fn type_errors() {
                 bar: Value::String("a".to_string())
             })
         }),
-        "invalid type: string \"a\", expected isize",
-        "invalid type: string \"a\", expected isize"
+        "invalid type: string \"a\", expected isize for key `foo.bar`",
+        "invalid type: string \"a\", expected isize for key `foo.bar`"
     }
 }
 
@@ -470,3 +470,65 @@ fn json_interoperability() {
     )
     .unwrap();
 }
+
+#[test]
+fn error_includes_key() {
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Package {
+        name: String,
+        version: String,
+        authors: Vec<String>,
+        profile: Profile,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Profile {
+        dev: Dev,
+    }
+
+    #[derive(Debug, Serialize, Deserialize)]
+    struct Dev {
+        debug: U32OrBool,
+    }
+
+    #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
+    #[serde(untagged, expecting = "expected a boolean or an integer")]
+    pub enum U32OrBool {
+        U32(u32),
+        Bool(bool),
+    }
+
+    let res: Result<Package, _> = toml_edit::de::from_str(
+        r#"
+[package]
+name = "foo"
+version = "0.0.0"
+authors = []
+
+[profile.dev]
+debug = 'a'
+"#,
+    );
+    let err = res.unwrap_err();
+    assert_eq!(
+        err.to_string(),
+        "expected a boolean or an integer for key `profile.dev.debug`"
+    );
+
+    let res: Result<Package, _> = toml_edit::de::from_str(
+        r#"
+[package]
+name = "foo"
+version = "0.0.0"
+authors = []
+
+[profile]
+dev = { debug = 'a' }
+"#,
+    );
+    let err = res.unwrap_err();
+    assert_eq!(
+        err.to_string(),
+        "expected a boolean or an integer for key `profile.dev.debug`"
+    );
+}