Repository: tafia/quick-xml
Pull request #100 (2017-12-07 19:53:00)

Migrate from 'error-chain' to 'failure'.
**Do not merge**

I've introduced enums for different errors. There are still few string-based errors.
I'm not sure if such granularity of errors is needed. Docs are also missing.

tafia: This is a breaking change that is very expected, thank you for all your work!

Do you see any drawback in using failure over error-chain so far? 
How was the transition (I want to use it on some other libs because of the no_std).
andy128k: @tafia 
`error-chain`'s macro is a custom syntax and gives cryptic errors if you make a typo.
In opposite, syntax of `failure` is just a rust with annotation. (Frankly speaking, `failure` also could be cryptic if you make error in `#[fail()]` annotation).

`error-chain` gives a single error type for your module with finite list of possible errors (ErrorKind enum). But `failure::Error` is an umbrella which can hold ANY error. So, maybe `failure::Error` is not a good choice for a library and custom error type should be defined (with derived Fail).

As soon as this error type is a part of library's API, it must be well defined. It could be enum (to mimic `error-chain`) or just a string-based struct (for simplicity). This is the question to be answered before any migrations.

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 2149cf5..268e5c2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ travis-ci = { repository = "tafia/quick-xml" }
 
 [dependencies]
 encoding_rs = "0.7.1"
-error-chain = "0.11.0"
+failure = "0.1.1"
 memchr = "2.0.0"
 
 [dev-dependencies]
diff --git a/src/errors.rs b/src/errors.rs
index e07e3de..05c24e5 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -2,22 +2,46 @@
 
 #![allow(missing_docs)]
 
-error_chain! {
-    foreign_links {
-        Io(::std::io::Error);
-    }
-    errors {
-        EndEventMismatch(expected: String, found: String) {
-            description("end event name mismatch with last start event name")
-            display("expecting </{}> found </{}>", expected, found)
-        }
-        Attribute(msg: String, i: usize) {
-            description("error while parsing attributes")
-            display("error while parsing attribute at position {}: {}", i, msg)
-        }
-        Escape(msg: String, range: ::std::ops::Range<usize>) {
-            description("error while escaping bytes")
-            display("Error while escaping character at range {:?}: {}", range, msg)
-        }
-    }
+#[derive(Fail, Debug)]
+pub enum Error {
+    #[fail(display = "I/O error")]
+    Io(#[cause] ::std::io::Error),
+
+    #[fail(display = "UTF8 error")]
+    Utf8(#[cause] ::std::str::Utf8Error),
+
+    #[fail(display = "Unexpected EOF during reading {}.", _0)]
+    UnexpectedEof(String),
+
+    #[fail(display = "Expecting </{}> found </{}>", expected, found)]
+    EndEventMismatch { expected: String, found: String },
+
+    #[fail(display = "Unexpected token '{}'", _0)]
+    UnexpectedToken(String),
+
+    #[fail(display = "Only Comment, CDATA and DOCTYPE nodes can start with a '!'")]
+    UnexpectedBang,
+
+    #[fail(display = "Cannot read text, expecting Event::Text")]
+    TextNotFound,
+
+    #[fail(display = "XmlDecl must start with 'version' attribute, found {:?}", _0)]
+    XmlDeclWithoutVersion(Option<String>),
+
+    #[fail(display = "error while parsing attribute at position {}: Attribute key cannot contain quote.", _0)]
+    NameWithQuote(usize),
+
+    #[fail(display = "error while parsing attribute at position {}: Attribute key must be directly followed by = or space", _0)]
+    NoEqAfterName(usize),
+
+    #[fail(display = "error while parsing attribute at position {}: Attribute value must start with a quote.", _0)]
+    UnquotedValue(usize),
+
+    #[fail(display = "error while parsing attribute at position {}: Duplicate attribute at position {} and {}", _0, _1, _0)]
+    DuplicatedAttribute(usize, usize),
+
+    #[fail(display = "{}", _0)]
+    EscapeError(#[cause] ::escape::EscapeError),
 }
+
+pub type Result<T> = ::std::result::Result<T, Error>;
diff --git a/src/escape.rs b/src/escape.rs
index ee88935..7c61e16 100644
--- a/src/escape.rs
+++ b/src/escape.rs
@@ -1,10 +1,33 @@
 //! Manage xml character escapes
 
 use std::borrow::Cow;
-use errors::Result;
-use errors::ErrorKind::Escape;
 use memchr;
 
+#[allow(missing_docs)]
+#[derive(Fail, Debug)]
+pub enum EscapeError {
+    #[fail(display = "Error while escaping character at range {:?}: Null character entity not allowed", _0)]
+    EntityWithNull(::std::ops::Range<usize>),
+
+    #[fail(display = "Error while escaping character at range {:?}: Unrecognized escape symbol: {:?}", _0, _1)]
+    UnrecognizedSymbol(::std::ops::Range<usize>, ::std::result::Result<String, ::std::string::FromUtf8Error>),
+
+    #[fail(display = "Error while escaping character at range {:?}: Cannot find ';' after '&'", _0)]
+    UnterminatedEntity(::std::ops::Range<usize>),
+
+    #[fail(display = "Cannot convert hexadecimal to utf8")]
+    TooLongHexadecimal,
+
+    #[fail(display = "'{}' is not a valid hexadecimal character", _0)]
+    InvalidHexadecimal(char),
+
+    #[fail(display = "Cannot convert decimal to utf8")]
+    TooLongDecimal,
+
+    #[fail(display = "'{}' is not a valid decimal character", _0)]
+    InvalidDecimal(char),
+}
+
 // UTF-8 ranges and tags for encoding characters
 const TAG_CONT: u8 = 0b1000_0000;
 const TAG_TWO_B: u8 = 0b1100_0000;
@@ -67,7 +90,7 @@ pub fn escape(raw: &[u8]) -> Cow<[u8]> {
 
 /// helper function to unescape a `&[u8]` and replace all
 /// xml escaped characters ('&...;') into their corresponding value
-pub fn unescape(raw: &[u8]) -> Result<Cow<[u8]>> {
+pub fn unescape(raw: &[u8]) -> Result<Cow<[u8]>, EscapeError> {
     let mut escapes = Vec::new();
 
     let mut start = 0;
@@ -82,29 +105,20 @@ pub fn unescape(raw: &[u8]) -> Result<Cow<[u8]>> {
                 b"amp" => ByteOrChar::Byte(b'&'),
                 b"apos" => ByteOrChar::Byte(b'\''),
                 b"quot" => ByteOrChar::Byte(b'\"'),
-                b"#x0" | b"#0" => bail!(Escape(
-                    "Null character entity not allowed".to_string(),
-                    start..end
-                )),
+                b"#x0" | b"#0" => return Err(EscapeError::EntityWithNull(start..end)),
                 bytes if bytes.starts_with(b"#x") => {
                     ByteOrChar::Char(parse_hexadecimal(&bytes[2..])?)
                 }
                 bytes if bytes.starts_with(b"#") => ByteOrChar::Char(parse_decimal(&bytes[1..])?),
-                bytes => bail!(Escape(
-                    format!(
-                        "Unrecognized escape symbol: {:?}",
-                        ::std::str::from_utf8(bytes)
-                    ),
-                    start..end
+                bytes => return Err(EscapeError::UnrecognizedSymbol(
+                    start..end,
+                    String::from_utf8(bytes.to_vec())
                 )),
             };
             escapes.push((start - 1..end, b_o_c));
             start = end + 1;
         } else {
-            bail!(Escape(
-                "Cannot find ';' after '&'".to_string(),
-                start..raw.len()
-            ));
+            return Err(EscapeError::UnterminatedEntity(start..raw.len()));
         }
     }
     if escapes.is_empty() {
@@ -146,10 +160,10 @@ fn push_utf8(buf: &mut Vec<u8>, code: u32) {
     }
 }
 
-fn parse_hexadecimal(bytes: &[u8]) -> Result<u32> {
+fn parse_hexadecimal(bytes: &[u8]) -> Result<u32, EscapeError> {
     // maximum code is 0x10FFFF => 6 characters
     if bytes.len() > 6 {
-        bail!("Cannot convert hexadecimal to utf8");
+        return Err(EscapeError::TooLongHexadecimal);
     }
     let mut code = 0;
     for &b in bytes {
@@ -158,23 +172,23 @@ fn parse_hexadecimal(bytes: &[u8]) -> Result<u32> {
             b'0'...b'9' => b - b'0',
             b'a'...b'f' => b - b'a' + 10,
             b'A'...b'F' => b - b'A' + 10,
-            b => bail!("'{}' is not a valid hexadecimal character", b as char),
+            b => return Err(EscapeError::InvalidHexadecimal(b as char)),
         } as u32;
     }
     Ok(code)
 }
 
-fn parse_decimal(bytes: &[u8]) -> Result<u32> {
+fn parse_decimal(bytes: &[u8]) -> Result<u32, EscapeError> {
     // maximum code is 0x10FFFF = 1114111 => 7 characters
     if bytes.len() > 7 {
-        bail!("Cannot convert decimal to utf8");
+        return Err(EscapeError::TooLongDecimal);
     }
     let mut code = 0;
     for &b in bytes {
         code *= 10;
         code += match b {
             b'0'...b'9' => b - b'0',
-            b => bail!("'{}' is not a valid decimal character", b as char),
+            b => return Err(EscapeError::InvalidDecimal(b as char)),
         } as u32;
     }
     Ok(code)
diff --git a/src/events/attributes.rs b/src/events/attributes.rs
index 4d5938b..1281fc7 100644
--- a/src/events/attributes.rs
+++ b/src/events/attributes.rs
@@ -4,7 +4,7 @@
 use std::borrow::Cow;
 use std::ops::Range;
 use std::io::BufRead;
-use errors::Result;
+use errors::{Error, Result};
 use escape::{escape, unescape};
 use reader::{is_whitespace, Reader};
 
@@ -45,9 +45,9 @@ impl<'a> Attributes<'a> {
     }
 
     /// sets `self.exit = true` to terminate the iterator
-    fn error<S: Into<String>>(&mut self, msg: S, p: usize) -> Result<Attribute<'a>> {
+    fn error(&mut self, err: Error) -> Result<Attribute<'a>> {
         self.exit = true;
-        Err(::errors::ErrorKind::Attribute(msg.into(), p).into())
+        Err(err.into())
     }
 }
 
@@ -66,7 +66,7 @@ pub struct Attribute<'a> {
 impl<'a> Attribute<'a> {
     /// unescapes the value
     pub fn unescaped_value(&self) -> Result<Cow<[u8]>> {
-        unescape(&*self.value)
+        unescape(&*self.value).map_err(Error::EscapeError)
     }
 
     /// unescapes then decode the value
@@ -173,20 +173,14 @@ impl<'a> Iterator for Attributes<'a> {
                 .iter()
                 .position(|&b| b == b'\'' || b == b'"')
             {
-                return Some(self.error("Attribute key cannot contain quote", start_key + i));
+                return Some(self.error(Error::NameWithQuote(start_key + i)));
             }
             if let Some(r) = self.consumed
                 .iter()
                 .cloned()
                 .find(|ref r| &self.bytes[(**r).clone()] == &self.bytes[start_key..end_key])
             {
-                return Some(self.error(
-                    format!(
-                        "Duplicate attribute at position {} and {}",
-                        r.start, start_key
-                    ),
-                    start_key,
-                ));
+                return Some(self.error(Error::DuplicatedAttribute(start_key, r.start)));
             }
             self.consumed.push(start_key..end_key);
         }
@@ -205,10 +199,7 @@ impl<'a> Iterator for Attributes<'a> {
                 .iter()
                 .position(|&b| !is_whitespace(b))
             {
-                return Some(self.error(
-                    "Attribute key must be directly followed by = or space",
-                    end_key + i,
-                ));
+                return Some(self.error(Error::NoEqAfterName(end_key + i)));
             }
         }
 
@@ -221,7 +212,7 @@ impl<'a> Iterator for Attributes<'a> {
         {
             Some((i, b @ &b'\'')) | Some((i, b @ &b'"')) => (*b, start_val + i + 1),
             Some((i, _)) => {
-                return Some(self.error("Attribute value must start with a quote", start_val + i));
+                return Some(self.error(Error::UnquotedValue(start_val + i)));
             }
             None => {
                 self.position = len;
diff --git a/src/events/mod.rs b/src/events/mod.rs
index 8f8fd28..4988a91 100644
--- a/src/events/mod.rs
+++ b/src/events/mod.rs
@@ -10,7 +10,7 @@ use std::io::BufRead;
 
 use escape::{escape, unescape};
 use self::attributes::{Attribute, Attributes};
-use errors::Result;
+use errors::{Error, Result};
 use reader::Reader;
 
 use memchr;
@@ -85,7 +85,7 @@ impl<'a> BytesStart<'a> {
     /// Searches for '&' into content and try to escape the coded character if possible
     /// returns Malformed error with index within element if '&' is not followed by ';'
     pub fn unescaped(&self) -> Result<Cow<[u8]>> {
-        unescape(&*self.buf)
+        unescape(&*self.buf).map_err(Error::EscapeError)
     }
 
     /// gets attributes iterator
@@ -155,11 +155,11 @@ impl<'a> BytesDecl<'a> {
                 key: b"version",
                 value: v,
             })) => Ok(v),
-            Some(Ok(a)) => Err(format!(
-                "XmlDecl must start with 'version' attribute, found {:?}",
-                from_utf8(a.key)
-            ).into()),
-            None => Err("XmlDecl must start with 'version' attribute, found none".into()),
+            Some(Ok(a)) => {
+                let found = from_utf8(a.key).map_err(Error::Utf8)?.to_string();
+                Err(Error::XmlDeclWithoutVersion(Some(found)))
+            },
+            None => Err(Error::XmlDeclWithoutVersion(None)),
         }
     }
 
@@ -327,7 +327,7 @@ impl<'a> BytesText<'a> {
     /// Searches for '&' into content and try to escape the coded character if possible
     /// returns Malformed error with index within element if '&' is not followed by ';'
     pub fn unescaped(&self) -> Result<Cow<[u8]>> {
-        unescape(self)
+        unescape(self).map_err(Error::EscapeError)
     }
 
     /// helper method to unescape then decode self using the reader encoding
diff --git a/src/lib.rs b/src/lib.rs
index c05038f..e854ef6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -107,7 +107,7 @@
 
 extern crate encoding_rs;
 #[macro_use]
-extern crate error_chain;
+extern crate failure;
 extern crate memchr;
 
 pub mod errors;
diff --git a/src/reader.rs b/src/reader.rs
index 1f6d5c3..40b46d3 100644
--- a/src/reader.rs
+++ b/src/reader.rs
@@ -8,7 +8,7 @@ use std::str::from_utf8;
 
 use encoding_rs::Encoding;
 
-use errors::{ErrorKind, Result};
+use errors::{Error, Result};
 use events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
 use events::attributes::Attribute;
 
@@ -197,7 +197,7 @@ impl<B: BufRead> Reader<B> {
             };
 
             // throw the error we couldn't throw in the block above because `self` was sill borrowed
-            start = start_result?;
+            start = start_result.map_err(Error::Io)?;
 
             // We intentionally don't `consume()` the byte, otherwise we would have to handle things
             // like '<>' here already.
@@ -245,21 +245,21 @@ impl<B: BufRead> Reader<B> {
                 Some(start) => {
                     if buf[1..] != self.opened_buffer[start..] {
                         self.buf_position -= len;
-                        bail!(ErrorKind::EndEventMismatch(
-                            from_utf8(&self.opened_buffer[start..])
+                        return Err(Error::EndEventMismatch {
+                            expected: from_utf8(&self.opened_buffer[start..])
                                 .unwrap_or("")
                                 .to_owned(),
-                            from_utf8(&buf[1..]).unwrap_or("").to_owned()
-                        ));
+                            found: from_utf8(&buf[1..]).unwrap_or("").to_owned()
+                        });
                     }
                     self.opened_buffer.truncate(start);
                 }
                 None => {
                     self.buf_position -= len;
-                    bail!(ErrorKind::EndEventMismatch(
-                        "".to_owned(),
-                        from_utf8(&buf[1..]).unwrap_or("").to_owned()
-                    ));
+                    return Err(Error::EndEventMismatch {
+                        expected: "".to_owned(),
+                        found: from_utf8(&buf[1..]).unwrap_or("").to_owned()
+                    });
                 }
             }
         }
@@ -284,7 +284,7 @@ impl<B: BufRead> Reader<B> {
                 match read_until(&mut self.reader, b'>', buf) {
                     Ok(0) => {
                         self.buf_position -= len;
-                        bail!(io_eof("Comment"))
+                        return Err(Error::UnexpectedEof("Comment".to_string()));
                     }
                     Ok(n) => self.buf_position += n,
                     Err(e) => return Err(e.into()),
@@ -296,7 +296,7 @@ impl<B: BufRead> Reader<B> {
                 for w in buf[buf_start + 3..len - 1].windows(2) {
                     if &*w == b"--" {
                         self.buf_position -= offset;
-                        bail!("Unexpected token '--'");
+                        return Err(Error::UnexpectedToken("--".to_string()));
                     }
                     offset -= 1;
                 }
@@ -313,7 +313,7 @@ impl<B: BufRead> Reader<B> {
                         match read_until(&mut self.reader, b'>', buf) {
                             Ok(0) => {
                                 self.buf_position -= len;
-                                bail!(io_eof("CData"));
+                                return Err(Error::UnexpectedEof("CData".to_string()));
                             }
                             Ok(n) => self.buf_position += n,
                             Err(e) => return Err(e.into()),
@@ -331,7 +331,7 @@ impl<B: BufRead> Reader<B> {
                         match read_until(&mut self.reader, b'>', buf) {
                             Ok(0) => {
                                 self.buf_position -= buf.len();
-                                bail!(io_eof("DOCTYPE"));
+                                return Err(Error::UnexpectedEof("DOCTYPE".to_string()));
                             }
                             Ok(n) => {
                                 self.buf_position += n;
@@ -347,11 +347,11 @@ impl<B: BufRead> Reader<B> {
                         &buf[buf_start + 8..len],
                     )))
                 }
-                _ => bail!("Only Comment, CDATA and DOCTYPE nodes can start with a '!'"),
+                _ => return Err(Error::UnexpectedBang)
             }
         } else {
             self.buf_position -= buf.len();
-            bail!("Only Comment, CDATA and DOCTYPE nodes can start with a '!'");
+            return Err(Error::UnexpectedBang);
         }
     }
 
@@ -372,7 +372,7 @@ impl<B: BufRead> Reader<B> {
             }
         } else {
             self.buf_position -= len;
-            bail!(io_eof("XmlDecl"));
+            Err(Error::UnexpectedEof("XmlDecl".to_string()))
         }
     }
 
@@ -603,7 +603,7 @@ impl<B: BufRead> Reader<B> {
                 Ok(Event::Start(ref e)) if e.name() == end => depth += 1,
                 Err(e) => return Err(e),
                 Ok(Event::Eof) => {
-                    return Err(io_eof(&format!("Expecting {:?} end", from_utf8(end))).into())
+                    return Err(Error::UnexpectedEof(format!("</{:?}>", from_utf8(end))));
                 }
                 _ => (),
             }
@@ -637,8 +637,8 @@ impl<B: BufRead> Reader<B> {
             Ok(Event::Text(e)) => e.unescape_and_decode(self),
             Ok(Event::End(ref e)) if e.name() == end.as_ref() => return Ok("".to_string()),
             Err(e) => return Err(e),
-            Ok(Event::Eof) => return Err(io_eof("Text").into()),
-            _ => return Err("Cannot read text, expecting Event::Text".into()),
+            Ok(Event::Eof) => return Err(Error::UnexpectedEof("Text".to_string())),
+            _ => return Err(Error::TextNotFound),
         };
         self.read_to_end(end, buf)?;
         s
@@ -648,7 +648,8 @@ impl<B: BufRead> Reader<B> {
 impl Reader<BufReader<File>> {
     /// Creates a xml reader from a file path
     pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Reader<BufReader<File>>> {
-        let reader = BufReader::new(try!(File::open(path)));
+        let file = File::open(path).map_err(Error::Io)?;
+        let reader = BufReader::new(file);
         Ok(Reader::from_reader(reader))
     }
 }
@@ -672,7 +673,7 @@ fn read_until<R: BufRead>(r: &mut R, byte: u8, buf: &mut Vec<u8>) -> Result<usiz
                 Ok(n) if n.is_empty() => return Ok(read),
                 Ok(n) => n,
                 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
-                Err(e) => bail!(e),
+                Err(e) => return Err(Error::Io(e)),
             };
 
             match memchr::memchr(byte, available) {
@@ -723,7 +724,7 @@ fn read_elem_until<R: BufRead>(r: &mut R, end_byte: u8, buf: &mut Vec<u8>) -> Re
                 Ok(n) if n.is_empty() => return Ok(read),
                 Ok(n) => n,
                 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
-                Err(e) => bail!(e),
+                Err(e) => return Err(Error::Io(e)),
             };
 
             let mut bytes = available.iter().enumerate();
@@ -943,7 +944,3 @@ impl NamespaceBufferIndex {
             .unwrap_or((None, qname))
     }
 }
-
-fn io_eof(msg: &str) -> ::std::io::Error {
-    ::std::io::Error::new(::std::io::ErrorKind::UnexpectedEof, msg.to_string())
-}
diff --git a/src/writer.rs b/src/writer.rs
index 3c42626..1d6c65e 100644
--- a/src/writer.rs
+++ b/src/writer.rs
@@ -2,7 +2,7 @@
 
 use std::io::Write;
 
-use errors::Result;
+use errors::{Error, Result};
 use events::Event;
 
 /// Xml writer
@@ -10,11 +10,14 @@ use events::Event;
 /// Consumes a `Write` and writes xml Events
 ///
 /// ```rust
+/// # extern crate failure;
+/// # extern crate quick_xml;
+/// # fn main() {
+/// use failure::Fail;
 /// use quick_xml::writer::Writer;
 /// use quick_xml::events::{Event, BytesEnd, BytesStart};
 /// use quick_xml::reader::Reader;
 /// use std::io::Cursor;
-/// use std::iter;
 ///
 /// let xml = r#"<this_tag k1="v1" k2="v2"><child>text</child></this_tag>"#;
 /// let mut reader = Reader::from_str(xml);
@@ -44,9 +47,9 @@ use events::Event;
 ///         Ok(Event::Eof) => break,
 ///         // we can either move or borrow the event to write, depending on your use-case
 ///         Ok(e) => assert!(writer.write_event(&e).is_ok()),
-///         // error are chained, the last one usually being the
+///         // errors are chained, the last one usually being the
 ///         // position where the error has happened
-///         Err(e) => panic!("{:?}", e.iter().map(|e| format!("{:?} -", e)).collect::<String>()),
+///         Err(e) => panic!("{:?}", e.causes().map(|e| format!("{:?} -", e)).collect::<String>()),
 ///     }
 ///     buf.clear();
 /// }
@@ -54,6 +57,7 @@ use events::Event;
 /// let result = writer.into_inner().into_inner();
 /// let expected = r#"<my_elem k1="v1" k2="v2" my-key="some value"><child>text</child></my_elem>"#;
 /// assert_eq!(result, expected.as_bytes());
+/// # }
 /// ```
 #[derive(Clone)]
 pub struct Writer<W: Write> {
@@ -91,11 +95,11 @@ impl<W: Write> Writer<W> {
     /// Writes bytes
     #[inline]
     pub fn write(&mut self, value: &[u8]) -> Result<usize> {
-        self.writer.write(value).map_err(|e| e.into())
+        self.writer.write(value).map_err(Error::Io)
     }
 
     #[inline]
     fn write_wrapped(&mut self, before: &[u8], value: &[u8], after: &[u8]) -> Result<usize> {
-        Ok(self.writer.write(before)? + self.writer.write(value)? + self.writer.write(after)?)
+        Ok(self.write(before)? + self.write(value)? + self.write(after)?)
     }
 }


Test patch:
diff --git a/tests/xmlrs_reader_tests.rs b/tests/xmlrs_reader_tests.rs
index 8b8fedb..294b513 100644
--- a/tests/xmlrs_reader_tests.rs
+++ b/tests/xmlrs_reader_tests.rs
@@ -1,5 +1,8 @@
+extern crate failure;
 extern crate quick_xml;
 
+use failure::Fail;
+
 use std::io::BufRead;
 use std::str::from_utf8;
 
@@ -93,12 +96,12 @@ fn sample_ns_short() {
 
 #[test]
 fn eof_1() {
-    test(br#"<?xml"#, br#"Error: XmlDecl"#, true);
+    test(br#"<?xml"#, br#"Error: Unexpected EOF during reading XmlDecl."#, true);
 }
 
 #[test]
 fn bad_1() {
-    test(br#"<?xml&.,"#, br#"1:6 Error: XmlDecl"#, true);
+    test(br#"<?xml&.,"#, br#"1:6 Error: Unexpected EOF during reading XmlDecl."#, true);
 }
 
 #[test]
@@ -422,7 +425,7 @@ impl<'a, 'b> fmt::Display for OptEvent<'a, 'b> {
             Ok((_, Event::PI(ref e))) => {
                 write!(f, "ProcessingInstruction(PI={:?})", from_utf8(e).unwrap())
             }
-            Err(ref e) => write!(f, "Error: {}", e.iter().last().unwrap()),
+            Err(ref e) => write!(f, "Error: {}", e.root_cause()),
             Ok((_, Event::DocType(ref e))) => write!(f, "DocType({})", from_utf8(e).unwrap()),
         }
     }