parsers: use Error type from xso

Jonas Schรคfer created

This is a large change and as such, it needs good motivation. Let me
remind you of the ultimate goal: we want a derive macro which allows us
to FromXml/IntoXml, and that derive macro should be usable from
`xmpp_parsers` and other crates.

For that, any code generated by the derive macro mustn't depend on any
code in the `xmpp_parsers` crate, because you cannot name the crate you
are in portably (`xmpp_parsers::..` wouldn't resolve within
`xmpp_parsers`, and `crate::..` would point at other crates if the macro
was used in other crates).

We also want to interoperate with code already implementing
`TryFrom<Element>` and `Into<Element>` on structs. This ultimately
requires that we have an error type which is shared by the two
implementations and that error type must be declared in the `xso` crate
to be usable by the macros.

Thus, we port the error type over to use the type declared in `xso`.

This changes the structure of the error type greatly; I do not think
that `xso` should have to know about all the different types we are
parsing there and they don't deserve special treatment. Wrapping them in
a `Box<dyn ..>` seems more appropriate.

Change summary

parsers/src/attention.rs             |   8 
parsers/src/avatar.rs                |   6 
parsers/src/bind.rs                  |  28 +--
parsers/src/blocking.rs              |  18 +-
parsers/src/bob.rs                   |  21 +-
parsers/src/bookmarks2.rs            |  25 +-
parsers/src/caps.rs                  |  10 
parsers/src/chatstates.rs            |   8 
parsers/src/data_forms.rs            |  50 +++---
parsers/src/date.rs                  |  41 +----
parsers/src/delay.rs                 |   6 
parsers/src/disco.rs                 |  44 +++---
parsers/src/ecaps2.rs                |  11 
parsers/src/eme.rs                   |   6 
parsers/src/forwarding.rs            |   4 
parsers/src/hashes.rs                |  14 +
parsers/src/http_upload.rs           |  11 
parsers/src/ibb.rs                   |  16 +
parsers/src/ibr.rs                   |  10 
parsers/src/idle.rs                  |  44 ++++-
parsers/src/iq.rs                    |  26 +-
parsers/src/jingle.rs                |  79 +++++-----
parsers/src/jingle_dtls_srtp.rs      |   4 
parsers/src/jingle_ft.rs             | 101 +++++++------
parsers/src/jingle_ibb.rs            |  22 ++
parsers/src/jingle_message.rs        |  18 +-
parsers/src/jingle_s5b.rs            |  35 ++--
parsers/src/lib.rs                   |   2 
parsers/src/mam.rs                   |  26 +-
parsers/src/mam_prefs.rs             |  16 +-
parsers/src/media_element.rs         |  32 +++-
parsers/src/message.rs               |  25 +-
parsers/src/message_correct.rs       |   8 
parsers/src/muc/muc.rs               |   6 
parsers/src/muc/user.rs              |  44 +++--
parsers/src/nick.rs                  |   8 
parsers/src/occupant_id.rs           |   6 
parsers/src/oob.rs                   |   4 
parsers/src/ping.rs                  |   8 
parsers/src/presence.rs              |  47 +++---
parsers/src/pubsub/event.rs          |  41 ++---
parsers/src/pubsub/owner.rs          |  15 +
parsers/src/pubsub/pubsub.rs         | 119 ++++++++--------
parsers/src/receipts.rs              |   4 
parsers/src/roster.rs                |  10 
parsers/src/rsm.rs                   |  42 ++--
parsers/src/rtt.rs                   |  24 +-
parsers/src/sasl.rs                  |  20 +-
parsers/src/server_info.rs           |  10 
parsers/src/stanza_error.rs          |  33 ++--
parsers/src/stanza_id.rs             |   8 
parsers/src/time.rs                  |  23 +-
parsers/src/tune.rs                  |  22 +-
parsers/src/util/error.rs            | 148 --------------------
parsers/src/util/macros.rs           | 218 ++++++++++++++---------------
parsers/src/util/mod.rs              |   3 
parsers/src/util/text_node_codecs.rs |  28 ++-
parsers/src/vcard.rs                 |   6 
parsers/src/xhtml.rs                 |  15 +
xso/src/error.rs                     |  84 +++++++++++
xso/src/lib.rs                       |  35 ++++
xso/src/minidom_compat.rs            |   2 
62 files changed, 916 insertions(+), 892 deletions(-)

Detailed changes

parsers/src/attention.rs ๐Ÿ”—

@@ -18,9 +18,9 @@ impl MessagePayload for Attention {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    #[cfg(not(feature = "disable-validation"))]
-    use crate::util::error::Error;
     use crate::Element;
+    #[cfg(not(feature = "disable-validation"))]
+    use xso::error::{Error, FromElementError};
 
     #[test]
     fn test_size() {
@@ -41,7 +41,7 @@ mod tests {
             .unwrap();
         let error = Attention::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in attention element.");
@@ -55,7 +55,7 @@ mod tests {
             .unwrap();
         let error = Attention::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in attention element.");

parsers/src/avatar.rs ๐Ÿ”—

@@ -58,9 +58,9 @@ impl PubSubPayload for Data {}
 mod tests {
     use super::*;
     use crate::hashes::Algo;
-    #[cfg(not(feature = "disable-validation"))]
-    use crate::util::error::Error;
     use crate::Element;
+    #[cfg(not(feature = "disable-validation"))]
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -119,7 +119,7 @@ mod tests {
             .unwrap();
         let error = Data::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in data element.")

parsers/src/bind.rs ๐Ÿ”—

@@ -6,10 +6,10 @@
 
 use crate::iq::{IqResultPayload, IqSetPayload};
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
 use jid::{FullJid, Jid};
 use std::str::FromStr;
+use xso::error::{Error, FromElementError};
 
 /// The request for resource binding, which is the process by which a client
 /// can obtain a full JID and start exchanging on the XMPP network.
@@ -34,23 +34,23 @@ impl BindQuery {
 impl IqSetPayload for BindQuery {}
 
 impl TryFrom<Element> for BindQuery {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<BindQuery, Error> {
+    fn try_from(elem: Element) -> Result<BindQuery, FromElementError> {
         check_self!(elem, "bind", BIND);
         check_no_attributes!(elem, "bind");
 
         let mut resource = None;
         for child in elem.children() {
             if resource.is_some() {
-                return Err(Error::ParseError("Bind can only have one child."));
+                return Err(Error::Other("Bind can only have one child.").into());
             }
             if child.is("resource", ns::BIND) {
                 check_no_attributes!(child, "resource");
                 check_no_children!(child, "resource");
                 resource = Some(child.text());
             } else {
-                return Err(Error::ParseError("Unknown element in bind request."));
+                return Err(Error::Other("Unknown element in bind request.").into());
             }
         }
 
@@ -93,32 +93,30 @@ impl From<BindResponse> for Jid {
 }
 
 impl TryFrom<Element> for BindResponse {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<BindResponse, Error> {
+    fn try_from(elem: Element) -> Result<BindResponse, FromElementError> {
         check_self!(elem, "bind", BIND);
         check_no_attributes!(elem, "bind");
 
         let mut jid = None;
         for child in elem.children() {
             if jid.is_some() {
-                return Err(Error::ParseError("Bind can only have one child."));
+                return Err(Error::Other("Bind can only have one child.").into());
             }
             if child.is("jid", ns::BIND) {
                 check_no_attributes!(child, "jid");
                 check_no_children!(child, "jid");
-                jid = Some(FullJid::from_str(&child.text())?);
+                jid = Some(FullJid::from_str(&child.text()).map_err(Error::text_parse_error)?);
             } else {
-                return Err(Error::ParseError("Unknown element in bind response."));
+                return Err(Error::Other("Unknown element in bind response.").into());
             }
         }
 
         Ok(BindResponse {
             jid: match jid {
                 None => {
-                    return Err(Error::ParseError(
-                        "Bind response must contain a jid element.",
-                    ))
+                    return Err(Error::Other("Bind response must contain a jid element.").into())
                 }
                 Some(jid) => jid,
             },
@@ -187,7 +185,7 @@ mod tests {
             .unwrap();
         let error = BindQuery::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in resource element.");
@@ -197,7 +195,7 @@ mod tests {
             .unwrap();
         let error = BindQuery::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in resource element.");

parsers/src/blocking.rs ๐Ÿ”—

@@ -6,9 +6,9 @@
 
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
+use xso::error::FromElementError;
 
 generate_empty_element!(
     /// The element requesting the blocklist, the result iq will contain a
@@ -30,9 +30,9 @@ macro_rules! generate_blocking_element {
         }
 
         impl TryFrom<Element> for $elem {
-            type Error = Error;
+            type Error = FromElementError;
 
-            fn try_from(elem: Element) -> Result<$elem, Error> {
+            fn try_from(elem: Element) -> Result<$elem, FromElementError> {
                 check_self!(elem, $name, BLOCKING);
                 check_no_attributes!(elem, $name);
                 let mut items = vec!();
@@ -96,6 +96,8 @@ generate_empty_element!(
 
 #[cfg(test)]
 mod tests {
+    use xso::error::Error;
+
     use super::*;
 
     #[cfg(target_pointer_width = "32")]
@@ -165,7 +167,7 @@ mod tests {
         let request_elem = elem.clone();
         let error = BlocklistRequest::try_from(request_elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in blocklist element.");
@@ -173,7 +175,7 @@ mod tests {
         let result_elem = elem.clone();
         let error = BlocklistResult::try_from(result_elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in blocklist element.");
@@ -183,7 +185,7 @@ mod tests {
             .unwrap();
         let error = Block::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in block element.");
@@ -193,7 +195,7 @@ mod tests {
             .unwrap();
         let error = Unblock::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in unblock element.");
@@ -205,7 +207,7 @@ mod tests {
         let elem: Element = "<blocklist xmlns='urn:xmpp:blocking'><item jid='coucou@coucou'/><item jid='domain'/></blocklist>".parse().unwrap();
         let error = BlocklistRequest::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in blocklist element.");

parsers/src/bob.rs ๐Ÿ”—

@@ -5,10 +5,10 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use crate::hashes::{Algo, Hash};
-use crate::util::error::Error;
 use crate::util::text_node_codecs::{Base64, Codec};
 use minidom::IntoAttributeValue;
 use std::str::FromStr;
+use xso::error::Error;
 
 /// A Content-ID, as defined in RFC2111.
 ///
@@ -27,11 +27,11 @@ impl FromStr for ContentId {
         let temp: Vec<_> = match temp[..] {
             [lhs, rhs] => {
                 if rhs != "bob.xmpp.org" {
-                    return Err(Error::ParseError("Wrong domain for cid URI."));
+                    return Err(Error::Other("Wrong domain for cid URI."));
                 }
                 lhs.splitn(2, '+').collect()
             }
-            _ => return Err(Error::ParseError("Missing @ in cid URI.")),
+            _ => return Err(Error::Other("Missing @ in cid URI.")),
         };
         let (algo, hex) = match temp[..] {
             [lhs, rhs] => {
@@ -42,9 +42,9 @@ impl FromStr for ContentId {
                 };
                 (algo, rhs)
             }
-            _ => return Err(Error::ParseError("Missing + in cid URI.")),
+            _ => return Err(Error::Other("Missing + in cid URI.")),
         };
-        let hash = Hash::from_hex(algo, hex)?;
+        let hash = Hash::from_hex(algo, hex).map_err(Error::text_parse_error)?;
         Ok(ContentId { hash })
     }
 }
@@ -89,6 +89,7 @@ generate_element!(
 mod tests {
     use super::*;
     use crate::Element;
+    use xso::error::FromElementError;
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -135,14 +136,14 @@ mod tests {
     fn invalid_cid() {
         let error = "Hello world!".parse::<ContentId>().unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            Error::Other(string) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Missing @ in cid URI.");
 
         let error = "Hello world@bob.xmpp.org".parse::<ContentId>().unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            Error::Other(string) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Missing + in cid URI.");
@@ -151,7 +152,7 @@ mod tests {
             .parse::<ContentId>()
             .unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            Error::Other(string) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Wrong domain for cid URI.");
@@ -160,7 +161,7 @@ mod tests {
             .parse::<ContentId>()
             .unwrap_err();
         let message = match error {
-            Error::ParseIntError(error) => error,
+            Error::TextParseError(error) if error.is::<std::num::ParseIntError>() => error,
             _ => panic!(),
         };
         assert_eq!(message.to_string(), "invalid digit found in string");
@@ -173,7 +174,7 @@ mod tests {
             .unwrap();
         let error = Data::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in data element.");

parsers/src/bookmarks2.rs ๐Ÿ”—

@@ -15,8 +15,8 @@
 //! This module exposes the [`Autojoin`][crate::bookmarks2::Autojoin] boolean flag, the [`Conference`][crate::bookmarks2::Conference] chatroom element, and the [BOOKMARKS2][crate::ns::BOOKMARKS2] XML namespace.
 
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
+use xso::error::{Error, FromElementError};
 
 generate_attribute!(
     /// Whether a conference bookmark should be joined automatically.
@@ -52,9 +52,9 @@ impl Conference {
 }
 
 impl TryFrom<Element> for Conference {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(root: Element) -> Result<Conference, Error> {
+    fn try_from(root: Element) -> Result<Conference, FromElementError> {
         check_self!(root, "conference", BOOKMARKS2, "Conference");
         check_no_unknown_attributes!(root, "Conference", ["autojoin", "name"]);
 
@@ -69,33 +69,30 @@ impl TryFrom<Element> for Conference {
         for child in root.children() {
             if child.is("nick", ns::BOOKMARKS2) {
                 if conference.nick.is_some() {
-                    return Err(Error::ParseError(
-                        "Conference must not have more than one nick.",
-                    ));
+                    return Err(Error::Other("Conference must not have more than one nick.").into());
                 }
                 check_no_children!(child, "nick");
                 check_no_attributes!(child, "nick");
                 conference.nick = Some(child.text());
             } else if child.is("password", ns::BOOKMARKS2) {
                 if conference.password.is_some() {
-                    return Err(Error::ParseError(
-                        "Conference must not have more than one password.",
-                    ));
+                    return Err(
+                        Error::Other("Conference must not have more than one password.").into(),
+                    );
                 }
                 check_no_children!(child, "password");
                 check_no_attributes!(child, "password");
                 conference.password = Some(child.text());
             } else if child.is("extensions", ns::BOOKMARKS2) {
                 if !conference.extensions.is_empty() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Conference must not have more than one extensions element.",
-                    ));
+                    )
+                    .into());
                 }
                 conference.extensions.extend(child.children().cloned());
             } else {
-                return Err(Error::ParseError(
-                    "Unknown element in bookmarks2 conference",
-                ));
+                return Err(Error::Other("Unknown element in bookmarks2 conference").into());
             }
         }
 

parsers/src/caps.rs ๐Ÿ”—

@@ -9,7 +9,6 @@ use crate::disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity};
 use crate::hashes::{Algo, Hash};
 use crate::ns;
 use crate::presence::PresencePayload;
-use crate::util::error::Error;
 use crate::Element;
 use base64::{engine::general_purpose::STANDARD as Base64, Engine};
 use blake2::Blake2bVar;
@@ -17,6 +16,7 @@ use digest::{Digest, Update, VariableOutput};
 use sha1::Sha1;
 use sha2::{Sha256, Sha512};
 use sha3::{Sha3_256, Sha3_512};
+use xso::error::{Error, FromElementError};
 
 /// Represents a capability hash for a given client.
 #[derive(Debug, Clone)]
@@ -39,16 +39,16 @@ pub struct Caps {
 impl PresencePayload for Caps {}
 
 impl TryFrom<Element> for Caps {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Caps, Error> {
+    fn try_from(elem: Element) -> Result<Caps, FromElementError> {
         check_self!(elem, "c", CAPS, "caps");
         check_no_children!(elem, "caps");
         check_no_unknown_attributes!(elem, "caps", ["hash", "ver", "ext", "node"]);
         let ver: String = get_attr!(elem, "ver", Required);
         let hash = Hash {
             algo: get_attr!(elem, "hash", Required),
-            hash: Base64.decode(ver)?,
+            hash: Base64.decode(ver).map_err(Error::text_parse_error)?,
         };
         Ok(Caps {
             ext: get_attr!(elem, "ext", Option),
@@ -253,7 +253,7 @@ mod tests {
         let elem: Element = "<c xmlns='http://jabber.org/protocol/caps'><hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=</hash></c>".parse().unwrap();
         let error = Caps::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in caps element.");

parsers/src/chatstates.rs ๐Ÿ”—

@@ -33,8 +33,8 @@ impl MessagePayload for ChatState {}
 mod tests {
     use super::*;
     use crate::ns;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[test]
     fn test_size() {
@@ -56,7 +56,7 @@ mod tests {
             .unwrap();
         let error = ChatState::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "This is not a chatstate element.");
@@ -70,7 +70,7 @@ mod tests {
             .unwrap();
         let error = ChatState::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in chatstate element.");
@@ -84,7 +84,7 @@ mod tests {
             .unwrap();
         let error = ChatState::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in chatstate element.");

parsers/src/data_forms.rs ๐Ÿ”—

@@ -6,8 +6,8 @@
 
 use crate::media_element::MediaElement;
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
+use xso::error::{Error, FromElementError};
 
 generate_element!(
     /// Represents one of the possible values for a list- field.
@@ -168,9 +168,9 @@ impl Field {
 }
 
 impl TryFrom<Element> for Field {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Field, Error> {
+    fn try_from(elem: Element) -> Result<Field, FromElementError> {
         check_self!(elem, "field", DATA_FORMS);
         check_no_unknown_attributes!(elem, "field", ["label", "type", "var"]);
         let mut field = Field {
@@ -185,7 +185,7 @@ impl TryFrom<Element> for Field {
         };
 
         if field.type_ != FieldType::Fixed && field.var.is_none() {
-            return Err(Error::ParseError("Required attribute 'var' missing."));
+            return Err(Error::Other("Required attribute 'var' missing.").into());
         }
 
         for element in elem.children() {
@@ -195,14 +195,14 @@ impl TryFrom<Element> for Field {
                 field.values.push(element.text());
             } else if element.is("required", ns::DATA_FORMS) {
                 if field.required {
-                    return Err(Error::ParseError("More than one required element."));
+                    return Err(Error::Other("More than one required element.").into());
                 }
                 check_no_children!(element, "required");
                 check_no_attributes!(element, "required");
                 field.required = true;
             } else if element.is("option", ns::DATA_FORMS) {
                 if !field.is_list() {
-                    return Err(Error::ParseError("Option element found in non-list field."));
+                    return Err(Error::Other("Option element found in non-list field.").into());
                 }
                 let option = Option_::try_from(element.clone())?;
                 field.options.push(option);
@@ -214,9 +214,9 @@ impl TryFrom<Element> for Field {
                 check_no_attributes!(element, "desc");
                 field.desc = Some(element.text());
             } else {
-                return Err(Error::ParseError(
-                    "Field child isnโ€™t a value, option or media element.",
-                ));
+                return Err(
+                    Error::Other("Field child isnโ€™t a value, option or media element.").into(),
+                );
             }
         }
         Ok(field)
@@ -299,9 +299,9 @@ impl DataForm {
 }
 
 impl TryFrom<Element> for DataForm {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<DataForm, Error> {
+    fn try_from(elem: Element) -> Result<DataForm, FromElementError> {
         check_self!(elem, "x", DATA_FORMS);
         check_no_unknown_attributes!(elem, "x", ["type"]);
         let type_ = get_attr!(elem, "type", Required);
@@ -315,16 +315,14 @@ impl TryFrom<Element> for DataForm {
         for child in elem.children() {
             if child.is("title", ns::DATA_FORMS) {
                 if form.title.is_some() {
-                    return Err(Error::ParseError("More than one title in form element."));
+                    return Err(Error::Other("More than one title in form element.").into());
                 }
                 check_no_children!(child, "title");
                 check_no_attributes!(child, "title");
                 form.title = Some(child.text());
             } else if child.is("instructions", ns::DATA_FORMS) {
                 if form.instructions.is_some() {
-                    return Err(Error::ParseError(
-                        "More than one instructions in form element.",
-                    ));
+                    return Err(Error::Other("More than one instructions in form element.").into());
                 }
                 check_no_children!(child, "instructions");
                 check_no_attributes!(child, "instructions");
@@ -334,17 +332,17 @@ impl TryFrom<Element> for DataForm {
                 if field.is_form_type(&form.type_) {
                     let mut field = field;
                     if form.form_type.is_some() {
-                        return Err(Error::ParseError("More than one FORM_TYPE in a data form."));
+                        return Err(Error::Other("More than one FORM_TYPE in a data form.").into());
                     }
                     if field.values.len() != 1 {
-                        return Err(Error::ParseError("Wrong number of values in FORM_TYPE."));
+                        return Err(Error::Other("Wrong number of values in FORM_TYPE.").into());
                     }
                     form.form_type = field.values.pop();
                 } else {
                     form.fields.push(field);
                 }
             } else {
-                return Err(Error::ParseError("Unknown child in data form element."));
+                return Err(Error::Other("Unknown child in data form element.").into());
             }
         }
         Ok(form)
@@ -415,7 +413,7 @@ mod tests {
                 .unwrap();
         let error = DataForm::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'var' missing.");
@@ -474,7 +472,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data'/>".parse().unwrap();
         let error = DataForm::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'type' missing.");
@@ -482,10 +480,10 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:data' type='coucou'/>".parse().unwrap();
         let error = DataForm::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
-            _ => panic!(),
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
+            other => panic!("unexpected result: {:?}", other),
         };
-        assert_eq!(message, "Unknown value for 'type' attribute.");
+        assert_eq!(message.to_string(), "Unknown value for 'type' attribute.");
     }
 
     #[test]
@@ -495,7 +493,7 @@ mod tests {
             .unwrap();
         let error = DataForm::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in data form element.");
@@ -516,7 +514,7 @@ mod tests {
             .unwrap();
         let error = Option_::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Missing child value in option element.");
@@ -524,7 +522,7 @@ mod tests {
         let elem: Element = "<option xmlns='jabber:x:data' label='Coucouโ€ฏ!'><value>coucou</value><value>error</value></option>".parse().unwrap();
         let error = Option_::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(

parsers/src/date.rs ๐Ÿ”—

@@ -4,7 +4,6 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-use crate::util::error::Error;
 use chrono::{DateTime as ChronoDateTime, FixedOffset};
 use minidom::{IntoAttributeValue, Node};
 use std::str::FromStr;
@@ -33,9 +32,9 @@ impl DateTime {
 }
 
 impl FromStr for DateTime {
-    type Err = Error;
+    type Err = chrono::ParseError;
 
-    fn from_str(s: &str) -> Result<DateTime, Error> {
+    fn from_str(s: &str) -> Result<DateTime, Self::Err> {
         Ok(DateTime(ChronoDateTime::parse_from_rfc3339(s)?))
     }
 }
@@ -80,51 +79,27 @@ mod tests {
     fn test_invalid_date() {
         // There is no thirteenth month.
         let error = DateTime::from_str("2017-13-01T12:23:34Z").unwrap_err();
-        let message = match error {
-            Error::ChronoParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message.to_string(), "input is out of range");
+        assert_eq!(error.to_string(), "input is out of range");
 
         // Timezone โ‰ฅ24:00 arenโ€™t allowed.
         let error = DateTime::from_str("2017-05-27T12:11:02+25:00").unwrap_err();
-        let message = match error {
-            Error::ChronoParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message.to_string(), "input is out of range");
+        assert_eq!(error.to_string(), "input is out of range");
 
         // Timezone without the : separator arenโ€™t allowed.
         let error = DateTime::from_str("2017-05-27T12:11:02+0100").unwrap_err();
-        let message = match error {
-            Error::ChronoParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message.to_string(), "input contains invalid characters");
+        assert_eq!(error.to_string(), "input contains invalid characters");
 
         // No seconds, error message could be improved.
         let error = DateTime::from_str("2017-05-27T12:11+01:00").unwrap_err();
-        let message = match error {
-            Error::ChronoParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message.to_string(), "input contains invalid characters");
+        assert_eq!(error.to_string(), "input contains invalid characters");
 
         // TODO: maybe weโ€™ll want to support this one, as per XEP-0082 ยง4.
         let error = DateTime::from_str("20170527T12:11:02+01:00").unwrap_err();
-        let message = match error {
-            Error::ChronoParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message.to_string(), "input contains invalid characters");
+        assert_eq!(error.to_string(), "input contains invalid characters");
 
         // No timezone.
         let error = DateTime::from_str("2017-05-27T12:11:02").unwrap_err();
-        let message = match error {
-            Error::ChronoParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message.to_string(), "premature end of input");
+        assert_eq!(error.to_string(), "premature end of input");
     }
 
     #[test]

parsers/src/delay.rs ๐Ÿ”—

@@ -32,10 +32,10 @@ impl PresencePayload for Delay {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
     use jid::BareJid;
     use std::str::FromStr;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -71,7 +71,7 @@ mod tests {
             .unwrap();
         let error = Delay::try_from(elem.clone()).unwrap_err();
         let returned_elem = match error {
-            Error::TypeMismatch(_, _, elem) => elem,
+            FromElementError::Mismatch(elem) => elem,
             _ => panic!(),
         };
         assert_eq!(elem, returned_elem);
@@ -84,7 +84,7 @@ mod tests {
             .unwrap();
         let error = Delay::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in delay element.");

parsers/src/disco.rs ๐Ÿ”—

@@ -8,9 +8,9 @@ use crate::data_forms::{DataForm, DataFormType};
 use crate::iq::{IqGetPayload, IqResultPayload};
 use crate::ns;
 use crate::rsm::{SetQuery, SetResult};
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
+use xso::error::{Error, FromElementError};
 
 generate_element!(
 /// Structure representing a `<query xmlns='http://jabber.org/protocol/disco#info'/>` element.
@@ -115,9 +115,9 @@ pub struct DiscoInfoResult {
 impl IqResultPayload for DiscoInfoResult {}
 
 impl TryFrom<Element> for DiscoInfoResult {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<DiscoInfoResult, Error> {
+    fn try_from(elem: Element) -> Result<DiscoInfoResult, FromElementError> {
         check_self!(elem, "query", DISCO_INFO, "disco#info result");
         check_no_unknown_attributes!(elem, "disco#info result", ["node"]);
 
@@ -138,30 +138,30 @@ impl TryFrom<Element> for DiscoInfoResult {
             } else if child.is("x", ns::DATA_FORMS) {
                 let data_form = DataForm::try_from(child.clone())?;
                 if data_form.type_ != DataFormType::Result_ {
-                    return Err(Error::ParseError(
-                        "Data form must have a 'result' type in disco#info.",
-                    ));
+                    return Err(
+                        Error::Other("Data form must have a 'result' type in disco#info.").into(),
+                    );
                 }
                 if data_form.form_type.is_none() {
-                    return Err(Error::ParseError("Data form found without a FORM_TYPE."));
+                    return Err(Error::Other("Data form found without a FORM_TYPE.").into());
                 }
                 result.extensions.push(data_form);
             } else {
-                return Err(Error::ParseError("Unknown element in disco#info."));
+                return Err(Error::Other("Unknown element in disco#info.").into());
             }
         }
 
         #[cfg(not(feature = "disable-validation"))]
         {
             if result.identities.is_empty() {
-                return Err(Error::ParseError(
-                    "There must be at least one identity in disco#info.",
-                ));
+                return Err(
+                    Error::Other("There must be at least one identity in disco#info.").into(),
+                );
             }
             if result.features.is_empty() {
-                return Err(Error::ParseError(
-                    "There must be at least one feature in disco#info.",
-                ));
+                return Err(
+                    Error::Other("There must be at least one feature in disco#info.").into(),
+                );
             }
         }
 
@@ -313,7 +313,7 @@ mod tests {
                 .unwrap();
         let error = DiscoInfoResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown element in disco#info.");
@@ -327,7 +327,7 @@ mod tests {
                 .unwrap();
         let error = DiscoInfoResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'category' missing.");
@@ -338,7 +338,7 @@ mod tests {
                 .unwrap();
         let error = DiscoInfoResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'category' must not be empty.");
@@ -346,7 +346,7 @@ mod tests {
         let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
         let error = DiscoInfoResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'type' missing.");
@@ -354,7 +354,7 @@ mod tests {
         let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou' type=''/></query>".parse().unwrap();
         let error = DiscoInfoResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'type' must not be empty.");
@@ -368,7 +368,7 @@ mod tests {
                 .unwrap();
         let error = DiscoInfoResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'var' missing.");
@@ -381,7 +381,7 @@ mod tests {
             .unwrap();
         let error = DiscoInfoResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(
@@ -392,7 +392,7 @@ mod tests {
         let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/></query>".parse().unwrap();
         let error = DiscoInfoResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "There must be at least one feature in disco#info.");

parsers/src/ecaps2.rs ๐Ÿ”—

@@ -9,12 +9,12 @@ use crate::disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity};
 use crate::hashes::{Algo, Hash};
 use crate::ns;
 use crate::presence::PresencePayload;
-use crate::util::error::Error;
 use base64::{engine::general_purpose::STANDARD as Base64, Engine};
 use blake2::Blake2bVar;
 use digest::{Digest, Update, VariableOutput};
 use sha2::{Sha256, Sha512};
 use sha3::{Sha3_256, Sha3_512};
+use xso::error::Error;
 
 generate_element!(
     /// Represents a set of capability hashes, all of them must correspond to
@@ -80,7 +80,7 @@ fn compute_identities(identities: &[Identity]) -> Vec<u8> {
 fn compute_extensions(extensions: &[DataForm]) -> Result<Vec<u8>, Error> {
     for extension in extensions {
         if extension.form_type.is_none() {
-            return Err(Error::ParseError("Missing FORM_TYPE in extension."));
+            return Err(Error::Other("Missing FORM_TYPE in extension."));
         }
     }
     Ok(compute_items(extensions, 0x1c, |extension| {
@@ -163,8 +163,8 @@ pub fn hash_ecaps2(data: &[u8], algo: Algo) -> Result<Hash, Error> {
                 hasher.finalize_variable(&mut vec).unwrap();
                 vec
             }
-            Algo::Sha_1 => return Err(Error::ParseError("Disabled algorithm sha-1: unsafe.")),
-            Algo::Unknown(_algo) => return Err(Error::ParseError("Unknown algorithm in ecaps2.")),
+            Algo::Sha_1 => return Err(Error::Other("Disabled algorithm sha-1: unsafe.").into()),
+            Algo::Unknown(_algo) => return Err(Error::Other("Unknown algorithm in ecaps2.").into()),
         },
         algo,
     })
@@ -187,6 +187,7 @@ pub fn query_ecaps2(hash: Hash) -> DiscoInfoQuery {
 mod tests {
     use super::*;
     use crate::Element;
+    use xso::error::FromElementError;
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -226,7 +227,7 @@ mod tests {
         let elem: Element = "<c xmlns='urn:xmpp:caps'><hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=</hash><hash xmlns='urn:xmpp:hashes:1' algo='sha3-256'>+sDTQqBmX6iG/X3zjt06fjZMBBqL/723knFIyRf0sg8=</hash></c>".parse().unwrap();
         let error = ECaps2::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in c element.");

parsers/src/eme.rs ๐Ÿ”—

@@ -24,8 +24,8 @@ impl MessagePayload for ExplicitMessageEncryption {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -61,7 +61,7 @@ mod tests {
             .unwrap();
         let error = ExplicitMessageEncryption::try_from(elem.clone()).unwrap_err();
         let returned_elem = match error {
-            Error::TypeMismatch(_, _, elem) => elem,
+            FromElementError::Mismatch(elem) => elem,
             _ => panic!(),
         };
         assert_eq!(elem, returned_elem);
@@ -74,7 +74,7 @@ mod tests {
             .unwrap();
         let error = ExplicitMessageEncryption::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in encryption element.");

parsers/src/forwarding.rs ๐Ÿ”—

@@ -26,8 +26,8 @@ generate_element!(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -54,7 +54,7 @@ mod tests {
             .unwrap();
         let error = Forwarded::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in forwarded element.");

parsers/src/hashes.rs ๐Ÿ”—

@@ -4,13 +4,13 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-use crate::util::error::Error;
 use crate::util::text_node_codecs::{Base64, Codec};
 use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
 use minidom::IntoAttributeValue;
 use std::num::ParseIntError;
 use std::ops::{Deref, DerefMut};
 use std::str::FromStr;
+use xso::error::Error;
 
 /// List of the algorithms we support, or Unknown.
 #[allow(non_camel_case_types)]
@@ -60,7 +60,7 @@ impl FromStr for Algo {
 
     fn from_str(s: &str) -> Result<Algo, Error> {
         Ok(match s {
-            "" => return Err(Error::ParseError("'algo' argument canโ€™t be empty.")),
+            "" => return Err(Error::Other("'algo' argument canโ€™t be empty.")),
 
             "sha-1" => Algo::Sha_1,
             "sha-256" => Algo::Sha_256,
@@ -118,7 +118,10 @@ impl Hash {
     /// Like [new](#method.new) but takes base64-encoded data before decoding
     /// it.
     pub fn from_base64(algo: Algo, hash: &str) -> Result<Hash, Error> {
-        Ok(Hash::new(algo, Base64Engine.decode(hash)?))
+        Ok(Hash::new(
+            algo,
+            Base64Engine.decode(hash).map_err(Error::text_parse_error)?,
+        ))
     }
 
     /// Like [new](#method.new) but takes hex-encoded data before decoding it.
@@ -205,6 +208,7 @@ impl Deref for Sha1HexAttribute {
 mod tests {
     use super::*;
     use crate::Element;
+    use xso::error::FromElementError;
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -255,7 +259,7 @@ mod tests {
             .unwrap();
         let error = Hash::try_from(elem.clone()).unwrap_err();
         let returned_elem = match error {
-            Error::TypeMismatch(_, _, elem) => elem,
+            FromElementError::Mismatch(elem) => elem,
             _ => panic!(),
         };
         assert_eq!(elem, returned_elem);
@@ -268,7 +272,7 @@ mod tests {
             .unwrap();
         let error = Hash::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in hash element.");

parsers/src/http_upload.rs ๐Ÿ”—

@@ -6,8 +6,8 @@
 
 use crate::iq::{IqGetPayload, IqResultPayload};
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
+use xso::error::{Error, FromElementError};
 
 generate_element!(
     /// Requesting a slot
@@ -40,8 +40,8 @@ pub enum Header {
 }
 
 impl TryFrom<Element> for Header {
-    type Error = Error;
-    fn try_from(elem: Element) -> Result<Header, Error> {
+    type Error = FromElementError;
+    fn try_from(elem: Element) -> Result<Header, FromElementError> {
         check_self!(elem, "header", HTTP_UPLOAD);
         check_no_children!(elem, "header");
         check_no_unknown_attributes!(elem, "header", ["name"]);
@@ -53,9 +53,10 @@ impl TryFrom<Element> for Header {
             "cookie" => Header::Cookie(text),
             "expires" => Header::Expires(text),
             _ => {
-                return Err(Error::ParseError(
+                return Err(Error::Other(
                     "Header name must be either 'Authorization', 'Cookie', or 'Expires'.",
-                ))
+                )
+                .into())
             }
         })
     }

parsers/src/ibb.rs ๐Ÿ”—

@@ -72,8 +72,8 @@ impl IqSetPayload for Close {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -131,7 +131,7 @@ mod tests {
             .unwrap();
         let error = Open::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'block-size' missing.");
@@ -141,7 +141,11 @@ mod tests {
             .unwrap();
         let error = Open::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseIntError(error) => error,
+            FromElementError::Invalid(Error::TextParseError(error))
+                if error.is::<std::num::ParseIntError>() =>
+            {
+                error
+            }
             _ => panic!(),
         };
         assert_eq!(message.to_string(), "invalid digit found in string");
@@ -151,7 +155,7 @@ mod tests {
             .unwrap();
         let error = Open::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(error) => error,
+            FromElementError::Invalid(Error::Other(error)) => error,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'sid' missing.");
@@ -162,9 +166,9 @@ mod tests {
         let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='128' sid='coucou' stanza='fdsq'/>".parse().unwrap();
         let error = Open::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'stanza' attribute.");
+        assert_eq!(message.to_string(), "Unknown value for 'stanza' attribute.");
     }
 }

parsers/src/ibr.rs ๐Ÿ”—

@@ -7,9 +7,9 @@
 use crate::data_forms::DataForm;
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
 use std::collections::HashMap;
+use xso::error::{Error, FromElementError};
 
 /// Query for registering against a service.
 #[derive(Debug, Clone)]
@@ -35,9 +35,9 @@ impl IqSetPayload for Query {}
 impl IqResultPayload for Query {}
 
 impl TryFrom<Element> for Query {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Query, Error> {
+    fn try_from(elem: Element) -> Result<Query, FromElementError> {
         check_self!(elem, "query", REGISTER, "IBR query");
         let mut query = Query {
             registered: false,
@@ -76,12 +76,12 @@ impl TryFrom<Element> for Query {
                 } else if name == "remove" {
                     query.remove = true;
                 } else {
-                    return Err(Error::ParseError("Wrong field in ibr element."));
+                    return Err(Error::Other("Wrong field in ibr element.").into());
                 }
             } else if child.is("x", ns::DATA_FORMS) {
                 query.form = Some(DataForm::try_from(child.clone())?);
             } else {
-                return Err(Error::ParseError("Unknown child in ibr element."));
+                return Err(Error::Other("Unknown child in ibr element.").into());
             }
         }
         Ok(query)

parsers/src/idle.rs ๐Ÿ”—

@@ -21,9 +21,9 @@ impl PresencePayload for Idle {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
     use std::str::FromStr;
+    use xso::error::{Error, FromElementError};
 
     #[test]
     fn test_size() {
@@ -45,7 +45,7 @@ mod tests {
             .unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in idle element.");
@@ -56,7 +56,7 @@ mod tests {
         let elem: Element = "<idle xmlns='urn:xmpp:idle:1'/>".parse().unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'since' missing.");
@@ -70,8 +70,12 @@ mod tests {
             .unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ChronoParseError(string) => string,
-            _ => panic!(),
+            FromElementError::Invalid(Error::TextParseError(string))
+                if string.is::<chrono::ParseError>() =>
+            {
+                string
+            }
+            other => panic!("unexpected result: {:?}", other),
         };
         assert_eq!(message.to_string(), "input is out of range");
 
@@ -81,7 +85,11 @@ mod tests {
             .unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ChronoParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string))
+                if string.is::<chrono::ParseError>() =>
+            {
+                string
+            }
             _ => panic!(),
         };
         assert_eq!(message.to_string(), "input is out of range");
@@ -92,7 +100,11 @@ mod tests {
             .unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ChronoParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string))
+                if string.is::<chrono::ParseError>() =>
+            {
+                string
+            }
             _ => panic!(),
         };
         assert_eq!(message.to_string(), "input contains invalid characters");
@@ -103,7 +115,11 @@ mod tests {
             .unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ChronoParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string))
+                if string.is::<chrono::ParseError>() =>
+            {
+                string
+            }
             _ => panic!(),
         };
         assert_eq!(message.to_string(), "input contains invalid characters");
@@ -114,7 +130,11 @@ mod tests {
             .unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ChronoParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string))
+                if string.is::<chrono::ParseError>() =>
+            {
+                string
+            }
             _ => panic!(),
         };
         assert_eq!(message.to_string(), "input contains invalid characters");
@@ -125,7 +145,11 @@ mod tests {
             .unwrap();
         let error = Idle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ChronoParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string))
+                if string.is::<chrono::ParseError>() =>
+            {
+                string
+            }
             _ => panic!(),
         };
         assert_eq!(message.to_string(), "premature end of input");

parsers/src/iq.rs ๐Ÿ”—

@@ -7,10 +7,10 @@
 
 use crate::ns;
 use crate::stanza_error::StanzaError;
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
 use minidom::IntoAttributeValue;
+use xso::error::{Error, FromElementError};
 
 /// Should be implemented on every known payload of an `<iq type='get'/>`.
 pub trait IqGetPayload: TryFrom<Element> + Into<Element> {}
@@ -139,9 +139,9 @@ impl Iq {
 }
 
 impl TryFrom<Element> for Iq {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(root: Element) -> Result<Iq, Error> {
+    fn try_from(root: Element) -> Result<Iq, FromElementError> {
         check_self!(root, "iq", DEFAULT_NS);
         let from = get_attr!(root, "from", Option);
         let to = get_attr!(root, "to", Option);
@@ -152,16 +152,16 @@ impl TryFrom<Element> for Iq {
         let mut error_payload = None;
         for elem in root.children() {
             if payload.is_some() {
-                return Err(Error::ParseError("Wrong number of children in iq element."));
+                return Err(Error::Other("Wrong number of children in iq element.").into());
             }
             if type_ == "error" {
                 if elem.is("error", ns::DEFAULT_NS) {
                     if error_payload.is_some() {
-                        return Err(Error::ParseError("Wrong number of children in iq element."));
+                        return Err(Error::Other("Wrong number of children in iq element.").into());
                     }
                     error_payload = Some(StanzaError::try_from(elem.clone())?);
                 } else if root.children().count() != 2 {
-                    return Err(Error::ParseError("Wrong number of children in iq element."));
+                    return Err(Error::Other("Wrong number of children in iq element.").into());
                 }
             } else {
                 payload = Some(elem.clone());
@@ -172,13 +172,13 @@ impl TryFrom<Element> for Iq {
             if let Some(payload) = payload {
                 IqType::Get(payload)
             } else {
-                return Err(Error::ParseError("Wrong number of children in iq element."));
+                return Err(Error::Other("Wrong number of children in iq element.").into());
             }
         } else if type_ == "set" {
             if let Some(payload) = payload {
                 IqType::Set(payload)
             } else {
-                return Err(Error::ParseError("Wrong number of children in iq element."));
+                return Err(Error::Other("Wrong number of children in iq element.").into());
             }
         } else if type_ == "result" {
             if let Some(payload) = payload {
@@ -190,10 +190,10 @@ impl TryFrom<Element> for Iq {
             if let Some(payload) = error_payload {
                 IqType::Error(payload)
             } else {
-                return Err(Error::ParseError("Wrong number of children in iq element."));
+                return Err(Error::Other("Wrong number of children in iq element.").into());
             }
         } else {
-            return Err(Error::ParseError("Unknown iq type."));
+            return Err(Error::Other("Unknown iq type.").into());
         };
 
         Ok(Iq {
@@ -251,7 +251,7 @@ mod tests {
         let elem: Element = "<iq xmlns='jabber:component:accept'/>".parse().unwrap();
         let error = Iq::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'id' missing.");
@@ -264,7 +264,7 @@ mod tests {
             .unwrap();
         let error = Iq::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'type' missing.");
@@ -418,7 +418,7 @@ mod tests {
             .unwrap();
         let error = Iq::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Wrong number of children in iq element.");

parsers/src/jingle.rs ๐Ÿ”—

@@ -11,12 +11,12 @@ use crate::jingle_ice_udp::Transport as IceUdpTransport;
 use crate::jingle_rtp::Description as RtpDescription;
 use crate::jingle_s5b::Transport as Socks5Transport;
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
 use std::collections::BTreeMap;
 use std::fmt;
 use std::str::FromStr;
+use xso::error::{Error, FromElementError};
 
 generate_attribute!(
     /// The action attribute.
@@ -428,7 +428,7 @@ impl FromStr for Reason {
             "unsupported-applications" => Reason::UnsupportedApplications,
             "unsupported-transports" => Reason::UnsupportedTransports,
 
-            _ => return Err(Error::ParseError("Unknown reason.")),
+            _ => return Err(Error::Other("Unknown reason.")),
         })
     }
 }
@@ -486,9 +486,9 @@ impl fmt::Display for ReasonElement {
 }
 
 impl TryFrom<Element> for ReasonElement {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<ReasonElement, Error> {
+    fn try_from(elem: Element) -> Result<ReasonElement, FromElementError> {
         check_self!(elem, "reason", JINGLE);
         check_no_attributes!(elem, "reason");
         let mut reason = None;
@@ -499,24 +499,22 @@ impl TryFrom<Element> for ReasonElement {
                 check_no_unknown_attributes!(child, "text", ["xml:lang"]);
                 let lang = get_attr!(elem, "xml:lang", Default);
                 if texts.insert(lang, child.text()).is_some() {
-                    return Err(Error::ParseError(
-                        "Text element present twice for the same xml:lang.",
-                    ));
+                    return Err(
+                        Error::Other("Text element present twice for the same xml:lang.").into(),
+                    );
                 }
             } else if child.has_ns(ns::JINGLE) {
                 if reason.is_some() {
-                    return Err(Error::ParseError(
-                        "Reason must not have more than one reason.",
-                    ));
+                    return Err(Error::Other("Reason must not have more than one reason.").into());
                 }
                 check_no_children!(child, "reason");
                 check_no_attributes!(child, "reason");
                 reason = Some(child.name().parse()?);
             } else {
-                return Err(Error::ParseError("Reason contains a foreign element."));
+                return Err(Error::Other("Reason contains a foreign element.").into());
             }
         }
-        let reason = reason.ok_or(Error::ParseError("Reason doesnโ€™t contain a valid reason."))?;
+        let reason = reason.ok_or(Error::Other("Reason doesnโ€™t contain a valid reason."))?;
         Ok(ReasonElement { reason, texts })
     }
 }
@@ -616,9 +614,9 @@ impl Jingle {
 }
 
 impl TryFrom<Element> for Jingle {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(root: Element) -> Result<Jingle, Error> {
+    fn try_from(root: Element) -> Result<Jingle, FromElementError> {
         check_self!(root, "jingle", JINGLE, "Jingle");
         check_no_unknown_attributes!(root, "Jingle", ["action", "initiator", "responder", "sid"]);
 
@@ -639,17 +637,13 @@ impl TryFrom<Element> for Jingle {
                 jingle.contents.push(content);
             } else if child.is("reason", ns::JINGLE) {
                 if jingle.reason.is_some() {
-                    return Err(Error::ParseError(
-                        "Jingle must not have more than one reason.",
-                    ));
+                    return Err(Error::Other("Jingle must not have more than one reason.").into());
                 }
                 let reason = ReasonElement::try_from(child)?;
                 jingle.reason = Some(reason);
             } else if child.is("group", ns::JINGLE_GROUPING) {
                 if jingle.group.is_some() {
-                    return Err(Error::ParseError(
-                        "Jingle must not have more than one grouping.",
-                    ));
+                    return Err(Error::Other("Jingle must not have more than one grouping.").into());
                 }
                 let group = Group::try_from(child)?;
                 jingle.group = Some(group);
@@ -726,7 +720,7 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1'/>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'action' missing.");
@@ -736,7 +730,7 @@ mod tests {
             .unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'sid' missing.");
@@ -746,10 +740,10 @@ mod tests {
             .unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'action' attribute.");
+        assert_eq!(message.to_string(), "Unknown value for 'action' attribute.");
     }
 
     #[test]
@@ -775,7 +769,7 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'creator' missing.");
@@ -783,7 +777,7 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'name' missing.");
@@ -791,26 +785,35 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
-            _ => panic!(),
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
+            other => panic!("unexpected result: {:?}", other),
         };
-        assert_eq!(message, "Unknown value for 'creator' attribute.");
+        assert_eq!(
+            message.to_string(),
+            "Unknown value for 'creator' attribute."
+        );
 
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'senders' attribute.");
+        assert_eq!(
+            message.to_string(),
+            "Unknown value for 'senders' attribute."
+        );
 
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'senders' attribute.");
+        assert_eq!(
+            message.to_string(),
+            "Unknown value for 'senders' attribute."
+        );
     }
 
     #[test]
@@ -833,7 +836,7 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Reason doesnโ€™t contain a valid reason.");
@@ -841,7 +844,7 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown reason.");
@@ -849,7 +852,7 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a xmlns='http://www.w3.org/1999/xhtml'/></reason></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Reason contains a foreign element.");
@@ -857,7 +860,7 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Jingle must not have more than one reason.");
@@ -865,7 +868,7 @@ mod tests {
         let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><text/><text/></reason></jingle>".parse().unwrap();
         let error = Jingle::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Text element present twice for the same xml:lang.");

parsers/src/jingle_dtls_srtp.rs ๐Ÿ”—

@@ -5,8 +5,8 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use crate::hashes::{Algo, Hash};
-use crate::util::error::Error;
 use crate::util::text_node_codecs::{Codec, ColonSeparatedHex};
+use xso::error::Error;
 
 generate_attribute!(
     /// Indicates which of the end points should initiate the TCP connection establishment.
@@ -64,7 +64,7 @@ impl Fingerprint {
         hash: &str,
     ) -> Result<Fingerprint, Error> {
         let algo = algo.parse()?;
-        let hash = Hash::from_colon_separated_hex(algo, hash)?;
+        let hash = Hash::from_colon_separated_hex(algo, hash).map_err(Error::text_parse_error)?;
         Ok(Fingerprint::from_hash(setup, hash))
     }
 }

parsers/src/jingle_ft.rs ๐Ÿ”—

@@ -8,10 +8,10 @@ use crate::date::DateTime;
 use crate::hashes::Hash;
 use crate::jingle::{ContentId, Creator};
 use crate::ns;
-use crate::util::error::Error;
 use minidom::{Element, Node};
 use std::collections::BTreeMap;
 use std::str::FromStr;
+use xso::error::{Error, FromElementError};
 
 generate_element!(
     /// Represents a range in a file.
@@ -85,7 +85,7 @@ impl File {
     /// Sets the date of last modification on this file from an ISO-8601
     /// string.
     pub fn with_date_str(mut self, date: &str) -> Result<File, Error> {
-        self.date = Some(DateTime::from_str(date)?);
+        self.date = Some(DateTime::from_str(date).map_err(Error::text_parse_error)?);
         Ok(self)
     }
 
@@ -127,9 +127,9 @@ impl File {
 }
 
 impl TryFrom<Element> for File {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<File, Error> {
+    fn try_from(elem: Element) -> Result<File, FromElementError> {
         check_self!(elem, "file", JINGLE_FT);
         check_no_attributes!(elem, "file");
 
@@ -146,43 +146,41 @@ impl TryFrom<Element> for File {
         for child in elem.children() {
             if child.is("date", ns::JINGLE_FT) {
                 if file.date.is_some() {
-                    return Err(Error::ParseError("File must not have more than one date."));
+                    return Err(Error::Other("File must not have more than one date.").into());
                 }
-                file.date = Some(child.text().parse()?);
+                file.date = Some(child.text().parse().map_err(Error::text_parse_error)?);
             } else if child.is("media-type", ns::JINGLE_FT) {
                 if file.media_type.is_some() {
-                    return Err(Error::ParseError(
-                        "File must not have more than one media-type.",
-                    ));
+                    return Err(Error::Other("File must not have more than one media-type.").into());
                 }
                 file.media_type = Some(child.text());
             } else if child.is("name", ns::JINGLE_FT) {
                 if file.name.is_some() {
-                    return Err(Error::ParseError("File must not have more than one name."));
+                    return Err(Error::Other("File must not have more than one name.").into());
                 }
                 file.name = Some(child.text());
             } else if child.is("desc", ns::JINGLE_FT) {
                 let lang = get_attr!(child, "xml:lang", Default);
                 let desc = Desc(child.text());
                 if file.descs.insert(lang, desc).is_some() {
-                    return Err(Error::ParseError(
-                        "Desc element present twice for the same xml:lang.",
-                    ));
+                    return Err(
+                        Error::Other("Desc element present twice for the same xml:lang.").into(),
+                    );
                 }
             } else if child.is("size", ns::JINGLE_FT) {
                 if file.size.is_some() {
-                    return Err(Error::ParseError("File must not have more than one size."));
+                    return Err(Error::Other("File must not have more than one size.").into());
                 }
-                file.size = Some(child.text().parse()?);
+                file.size = Some(child.text().parse().map_err(Error::text_parse_error)?);
             } else if child.is("range", ns::JINGLE_FT) {
                 if file.range.is_some() {
-                    return Err(Error::ParseError("File must not have more than one range."));
+                    return Err(Error::Other("File must not have more than one range.").into());
                 }
                 file.range = Some(Range::try_from(child.clone())?);
             } else if child.is("hash", ns::HASHES) {
                 file.hashes.push(Hash::try_from(child.clone())?);
             } else {
-                return Err(Error::ParseError("Unknown element in JingleFT file."));
+                return Err(Error::Other("Unknown element in JingleFT file.").into());
             }
         }
 
@@ -230,24 +228,25 @@ pub struct Description {
 }
 
 impl TryFrom<Element> for Description {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Description, Error> {
+    fn try_from(elem: Element) -> Result<Description, FromElementError> {
         check_self!(elem, "description", JINGLE_FT, "JingleFT description");
         check_no_attributes!(elem, "JingleFT description");
         let mut file = None;
         for child in elem.children() {
             if file.is_some() {
-                return Err(Error::ParseError(
+                return Err(Error::Other(
                     "JingleFT description element must have exactly one child.",
-                ));
+                )
+                .into());
             }
             file = Some(File::try_from(child.clone())?);
         }
         if file.is_none() {
-            return Err(Error::ParseError(
-                "JingleFT description element must have exactly one child.",
-            ));
+            return Err(
+                Error::Other("JingleFT description element must have exactly one child.").into(),
+            );
         }
         Ok(Description {
             file: file.unwrap(),
@@ -277,24 +276,30 @@ pub struct Checksum {
 }
 
 impl TryFrom<Element> for Checksum {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Checksum, Error> {
+    fn try_from(elem: Element) -> Result<Checksum, FromElementError> {
         check_self!(elem, "checksum", JINGLE_FT);
         check_no_unknown_attributes!(elem, "checksum", ["name", "creator"]);
         let mut file = None;
         for child in elem.children() {
             if file.is_some() {
-                return Err(Error::ParseError(
-                    "JingleFT checksum element must have exactly one child.",
-                ));
+                return Err(
+                    Error::Other("JingleFT checksum element must have exactly one child.").into(),
+                );
             }
-            file = Some(File::try_from(child.clone()).map_err(|e| e.hide_type_mismatch())?);
+            file = Some(match File::try_from(child.clone()) {
+                Ok(v) => v,
+                Err(FromElementError::Mismatch(_)) => {
+                    return Err(Error::Other("Unexpected child element").into())
+                }
+                Err(other) => return Err(other),
+            });
         }
         if file.is_none() {
-            return Err(Error::ParseError(
-                "JingleFT checksum element must have exactly one child.",
-            ));
+            return Err(
+                Error::Other("JingleFT checksum element must have exactly one child.").into(),
+            );
         }
         Ok(Checksum {
             name: get_attr!(elem, "name", Required),
@@ -451,7 +456,7 @@ mod tests {
         .unwrap();
         let error = Description::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Desc element present twice for the same xml:lang.");
@@ -471,7 +476,7 @@ mod tests {
         let elem: Element = "<received xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator'><coucou/></received>".parse().unwrap();
         let error = Received::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in received element.");
@@ -482,7 +487,7 @@ mod tests {
                 .unwrap();
         let error = Received::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'name' missing.");
@@ -490,10 +495,13 @@ mod tests {
         let elem: Element = "<received xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='coucou'/>".parse().unwrap();
         let error = Received::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'creator' attribute.");
+        assert_eq!(
+            message.to_string(),
+            "Unknown value for 'creator' attribute."
+        );
     }
 
     #[cfg(not(feature = "disable-validation"))]
@@ -502,7 +510,7 @@ mod tests {
         let elem: Element = "<received xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator' coucou=''/>".parse().unwrap();
         let error = Received::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in received element.");
@@ -540,7 +548,7 @@ mod tests {
         let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator'><coucou/></checksum>".parse().unwrap();
         let error = Checksum::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             other => panic!("unexpected error: {:?}", other),
         };
         assert_eq!(message, "Unexpected child element");
@@ -548,7 +556,7 @@ mod tests {
         let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' creator='initiator'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
         let error = Checksum::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'name' missing.");
@@ -556,10 +564,13 @@ mod tests {
         let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='coucou'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
         let error = Checksum::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'creator' attribute.");
+        assert_eq!(
+            message.to_string(),
+            "Unknown value for 'creator' attribute."
+        );
     }
 
     #[cfg(not(feature = "disable-validation"))]
@@ -568,7 +579,7 @@ mod tests {
         let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator' coucou=''><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
         let error = Checksum::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in checksum element.");
@@ -611,7 +622,7 @@ mod tests {
             .unwrap();
         let error = Range::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in range element.");

parsers/src/jingle_ibb.rs ๐Ÿ”—

@@ -24,8 +24,8 @@ attributes: [
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -58,7 +58,7 @@ mod tests {
             .unwrap();
         let error = Transport::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'block-size' missing.");
@@ -69,7 +69,11 @@ mod tests {
                 .unwrap();
         let error = Transport::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseIntError(error) => error,
+            FromElementError::Invalid(Error::TextParseError(error))
+                if error.is::<std::num::ParseIntError>() =>
+            {
+                error
+            }
             _ => panic!(),
         };
         assert_eq!(
@@ -82,7 +86,11 @@ mod tests {
             .unwrap();
         let error = Transport::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseIntError(error) => error,
+            FromElementError::Invalid(Error::TextParseError(error))
+                if error.is::<std::num::ParseIntError>() =>
+            {
+                error
+            }
             _ => panic!(),
         };
         assert_eq!(message.to_string(), "invalid digit found in string");
@@ -93,7 +101,7 @@ mod tests {
                 .unwrap();
         let error = Transport::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'sid' missing.");
@@ -104,9 +112,9 @@ mod tests {
         let elem: Element = "<transport xmlns='urn:xmpp:jingle:transports:ibb:1' block-size='128' sid='coucou' stanza='fdsq'/>".parse().unwrap();
         let error = Transport::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'stanza' attribute.");
+        assert_eq!(message.to_string(), "Unknown value for 'stanza' attribute.");
     }
 }

parsers/src/jingle_message.rs ๐Ÿ”—

@@ -6,8 +6,8 @@
 
 use crate::jingle::SessionId;
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
+use xso::error::{Error, FromElementError};
 
 /// Defines a protocol for broadcasting Jingle requests to all of the clients
 /// of a user.
@@ -47,27 +47,27 @@ fn check_empty_and_get_sid(elem: Element) -> Result<SessionId, Error> {
 }
 
 impl TryFrom<Element> for JingleMI {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<JingleMI, Error> {
+    fn try_from(elem: Element) -> Result<JingleMI, FromElementError> {
         if !elem.has_ns(ns::JINGLE_MESSAGE) {
-            return Err(Error::ParseError("This is not a Jingle message element."));
+            return Err(Error::Other("This is not a Jingle message element.").into());
         }
         Ok(match elem.name() {
             "propose" => {
                 let mut description = None;
                 for child in elem.children() {
                     if child.name() != "description" {
-                        return Err(Error::ParseError("Unknown child in propose element."));
+                        return Err(Error::Other("Unknown child in propose element.").into());
                     }
                     if description.is_some() {
-                        return Err(Error::ParseError("Too many children in propose element."));
+                        return Err(Error::Other("Too many children in propose element.").into());
                     }
                     description = Some(child.clone());
                 }
                 JingleMI::Propose {
                     sid: get_sid(elem)?,
-                    description: description.ok_or(Error::ParseError(
+                    description: description.ok_or(Error::Other(
                         "Propose element doesnโ€™t contain a description.",
                     ))?,
                 }
@@ -76,7 +76,7 @@ impl TryFrom<Element> for JingleMI {
             "accept" => JingleMI::Accept(check_empty_and_get_sid(elem)?),
             "proceed" => JingleMI::Proceed(check_empty_and_get_sid(elem)?),
             "reject" => JingleMI::Reject(check_empty_and_get_sid(elem)?),
-            _ => return Err(Error::ParseError("This is not a Jingle message element.")),
+            _ => return Err(Error::Other("This is not a Jingle message element.").into()),
         })
     }
 }
@@ -134,7 +134,7 @@ mod tests {
                 .unwrap();
         let error = JingleMI::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in propose element.");

parsers/src/jingle_s5b.rs ๐Ÿ”—

@@ -5,10 +5,10 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
 use std::net::IpAddr;
+use xso::error::{Error, FromElementError};
 
 generate_attribute!(
     /// The type of the connection being proposed by this candidate.
@@ -171,9 +171,9 @@ impl Transport {
 }
 
 impl TryFrom<Element> for Transport {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Transport, Error> {
+    fn try_from(elem: Element) -> Result<Transport, FromElementError> {
         check_self!(elem, "transport", JINGLE_S5B);
         check_no_unknown_attributes!(elem, "transport", ["sid", "dstaddr", "mode"]);
         let sid = get_attr!(elem, "sid", Required);
@@ -186,47 +186,50 @@ impl TryFrom<Element> for Transport {
                 let mut candidates =
                     match payload {
                         Some(TransportPayload::Candidates(candidates)) => candidates,
-                        Some(_) => return Err(Error::ParseError(
+                        Some(_) => return Err(Error::Other(
                             "Non-candidate child already present in JingleS5B transport element.",
-                        )),
+                        )
+                        .into()),
                         None => vec![],
                     };
                 candidates.push(Candidate::try_from(child.clone())?);
                 TransportPayload::Candidates(candidates)
             } else if child.is("activated", ns::JINGLE_S5B) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Non-activated child already present in JingleS5B transport element.",
-                    ));
+                    )
+                    .into());
                 }
                 let cid = get_attr!(child, "cid", Required);
                 TransportPayload::Activated(cid)
             } else if child.is("candidate-error", ns::JINGLE_S5B) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Non-candidate-error child already present in JingleS5B transport element.",
-                    ));
+                    )
+                    .into());
                 }
                 TransportPayload::CandidateError
             } else if child.is("candidate-used", ns::JINGLE_S5B) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Non-candidate-used child already present in JingleS5B transport element.",
-                    ));
+                    )
+                    .into());
                 }
                 let cid = get_attr!(child, "cid", Required);
                 TransportPayload::CandidateUsed(cid)
             } else if child.is("proxy-error", ns::JINGLE_S5B) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Non-proxy-error child already present in JingleS5B transport element.",
-                    ));
+                    )
+                    .into());
                 }
                 TransportPayload::ProxyError
             } else {
-                return Err(Error::ParseError(
-                    "Unknown child in JingleS5B transport element.",
-                ));
+                return Err(Error::Other("Unknown child in JingleS5B transport element.").into());
             });
         }
         let payload = payload.unwrap_or(TransportPayload::None);

parsers/src/lib.rs ๐Ÿ”—

@@ -23,7 +23,7 @@
 
 #![warn(missing_docs)]
 
-pub use crate::util::error::Error;
+pub use xso::error::{Error, FromElementError};
 // TODO: only export top-level module on the next major release
 pub use jid::{self, BareJid, Error as JidParseError, FullJid, Jid};
 pub use minidom::Element;

parsers/src/mam.rs ๐Ÿ”—

@@ -11,9 +11,9 @@ use crate::message::MessagePayload;
 use crate::ns;
 use crate::pubsub::NodeName;
 use crate::rsm::{SetQuery, SetResult};
-use crate::util::error::Error;
 use crate::Element;
 use minidom::Node;
+use xso::error::{Error, FromElementError};
 
 generate_id!(
     /// An identifier matching a result message to the query requesting it.
@@ -41,8 +41,8 @@ impl IqSetPayload for Query {}
 impl IqResultPayload for Query {}
 
 impl TryFrom<Element> for Query {
-    type Error = Error;
-    fn try_from(elem: Element) -> Result<Query, Error> {
+    type Error = FromElementError;
+    fn try_from(elem: Element) -> Result<Query, FromElementError> {
         check_self!(elem, "query", MAM);
         check_no_unknown_attributes!(elem, "query", ["queryid", "node"]);
 
@@ -52,32 +52,34 @@ impl TryFrom<Element> for Query {
         for child in elem.children() {
             if child.is("x", ns::DATA_FORMS) {
                 if form.is_some() {
-                    return Err(Error::ParseError(
-                        "Element query must not have more than one x child.",
-                    ));
+                    return Err(
+                        Error::Other("Element query must not have more than one x child.").into(),
+                    );
                 }
                 form = Some(DataForm::try_from(child.clone())?);
                 continue;
             }
             if child.is("set", ns::RSM) {
                 if set.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Element query must not have more than one set child.",
-                    ));
+                    )
+                    .into());
                 }
                 set = Some(SetQuery::try_from(child.clone())?);
                 continue;
             }
             if child.is("flip-page", ns::MAM) {
                 if flip_page.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Element query must not have more than one flip-page child.",
-                    ));
+                    )
+                    .into());
                 }
                 flip_page = Some(true);
                 continue;
             }
-            return Err(Error::ParseError("Unknown child in query element."));
+            return Err(Error::Other("Unknown child in query element.").into());
         }
         Ok(Query {
             queryid: match elem.attr("queryid") {
@@ -296,7 +298,7 @@ mod tests {
             .unwrap();
         let error = Query::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in query element.");

parsers/src/mam_prefs.rs ๐Ÿ”—

@@ -6,9 +6,9 @@
 
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::ns;
-use crate::util::error::Error;
 use jid::Jid;
 use minidom::{Element, Node};
+use xso::error::{Error, FromElementError};
 
 generate_attribute!(
     /// Notes the default archiving preference for the user.
@@ -44,9 +44,9 @@ impl IqSetPayload for Prefs {}
 impl IqResultPayload for Prefs {}
 
 impl TryFrom<Element> for Prefs {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Prefs, Error> {
+    fn try_from(elem: Element) -> Result<Prefs, FromElementError> {
         check_self!(elem, "prefs", MAM);
         check_no_unknown_attributes!(elem, "prefs", ["default"]);
         let mut always = vec![];
@@ -55,19 +55,19 @@ impl TryFrom<Element> for Prefs {
             if child.is("always", ns::MAM) {
                 for jid_elem in child.children() {
                     if !jid_elem.is("jid", ns::MAM) {
-                        return Err(Error::ParseError("Invalid jid element in always."));
+                        return Err(Error::Other("Invalid jid element in always.").into());
                     }
-                    always.push(jid_elem.text().parse()?);
+                    always.push(jid_elem.text().parse().map_err(Error::text_parse_error)?);
                 }
             } else if child.is("never", ns::MAM) {
                 for jid_elem in child.children() {
                     if !jid_elem.is("jid", ns::MAM) {
-                        return Err(Error::ParseError("Invalid jid element in never."));
+                        return Err(Error::Other("Invalid jid element in never.").into());
                     }
-                    never.push(jid_elem.text().parse()?);
+                    never.push(jid_elem.text().parse().map_err(Error::text_parse_error)?);
                 }
             } else {
-                return Err(Error::ParseError("Unknown child in prefs element."));
+                return Err(Error::Other("Unknown child in prefs element.").into());
             }
         }
         let default_ = get_attr!(elem, "default", Required);

parsers/src/media_element.rs ๐Ÿ”—

@@ -46,8 +46,8 @@ generate_element!(
 mod tests {
     use super::*;
     use crate::data_forms::DataForm;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -98,7 +98,11 @@ mod tests {
             .unwrap();
         let error = MediaElement::try_from(elem).unwrap_err();
         let error = match error {
-            Error::ParseIntError(error) => error,
+            FromElementError::Invalid(Error::TextParseError(error))
+                if error.is::<std::num::ParseIntError>() =>
+            {
+                error
+            }
             _ => panic!(),
         };
         assert_eq!(error.to_string(), "cannot parse integer from empty string");
@@ -108,7 +112,11 @@ mod tests {
             .unwrap();
         let error = MediaElement::try_from(elem).unwrap_err();
         let error = match error {
-            Error::ParseIntError(error) => error,
+            FromElementError::Invalid(Error::TextParseError(error))
+                if error.is::<std::num::ParseIntError>() =>
+            {
+                error
+            }
             _ => panic!(),
         };
         assert_eq!(error.to_string(), "invalid digit found in string");
@@ -118,7 +126,11 @@ mod tests {
             .unwrap();
         let error = MediaElement::try_from(elem).unwrap_err();
         let error = match error {
-            Error::ParseIntError(error) => error,
+            FromElementError::Invalid(Error::TextParseError(error))
+                if error.is::<std::num::ParseIntError>() =>
+            {
+                error
+            }
             _ => panic!(),
         };
         assert_eq!(error.to_string(), "cannot parse integer from empty string");
@@ -128,7 +140,11 @@ mod tests {
             .unwrap();
         let error = MediaElement::try_from(elem).unwrap_err();
         let error = match error {
-            Error::ParseIntError(error) => error,
+            FromElementError::Invalid(Error::TextParseError(error))
+                if error.is::<std::num::ParseIntError>() =>
+            {
+                error
+            }
             _ => panic!(),
         };
         assert_eq!(error.to_string(), "invalid digit found in string");
@@ -141,7 +157,7 @@ mod tests {
             .unwrap();
         let error = MediaElement::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in media element.");
@@ -155,7 +171,7 @@ mod tests {
                 .unwrap();
         let error = MediaElement::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'type' missing.");
@@ -165,7 +181,7 @@ mod tests {
             .unwrap();
         let error = MediaElement::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(

parsers/src/message.rs ๐Ÿ”—

@@ -5,10 +5,10 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
 use std::collections::BTreeMap;
+use xso::error::{Error, FromElementError};
 
 /// Should be implemented on every known payload of a `<message/>`.
 pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
@@ -213,7 +213,7 @@ impl Message {
     /// the message.
     ///
     /// Elements which do not match the given type are not removed.
-    pub fn extract_payload<T: TryFrom<Element, Error = Error>>(
+    pub fn extract_payload<T: TryFrom<Element, Error = FromElementError>>(
         &mut self,
     ) -> Result<Option<T>, Error> {
         let mut buf = Vec::with_capacity(self.payloads.len());
@@ -225,10 +225,10 @@ impl Message {
                     result = Ok(Some(v));
                     break;
                 }
-                Err(Error::TypeMismatch(_, _, residual)) => {
+                Err(FromElementError::Mismatch(residual)) => {
                     buf.push(residual);
                 }
-                Err(other) => {
+                Err(FromElementError::Invalid(other)) => {
                     result = Err(other);
                     break;
                 }
@@ -241,9 +241,9 @@ impl Message {
 }
 
 impl TryFrom<Element> for Message {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(root: Element) -> Result<Message, Error> {
+    fn try_from(root: Element) -> Result<Message, FromElementError> {
         check_self!(root, "message", DEFAULT_NS);
         let from = get_attr!(root, "from", Option);
         let to = get_attr!(root, "to", Option);
@@ -259,22 +259,23 @@ impl TryFrom<Element> for Message {
                 let lang = get_attr!(elem, "xml:lang", Default);
                 let body = Body(elem.text());
                 if bodies.insert(lang, body).is_some() {
-                    return Err(Error::ParseError(
-                        "Body element present twice for the same xml:lang.",
-                    ));
+                    return Err(
+                        Error::Other("Body element present twice for the same xml:lang.").into(),
+                    );
                 }
             } else if elem.is("subject", ns::DEFAULT_NS) {
                 check_no_children!(elem, "subject");
                 let lang = get_attr!(elem, "xml:lang", Default);
                 let subject = Subject(elem.text());
                 if subjects.insert(lang, subject).is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Subject element present twice for the same xml:lang.",
-                    ));
+                    )
+                    .into());
                 }
             } else if elem.is("thread", ns::DEFAULT_NS) {
                 if thread.is_some() {
-                    return Err(Error::ParseError("Thread element present twice."));
+                    return Err(Error::Other("Thread element present twice.").into());
                 }
                 check_no_children!(elem, "thread");
                 thread = Some(Thread(elem.text()));

parsers/src/message_correct.rs ๐Ÿ”—

@@ -21,8 +21,8 @@ impl MessagePayload for Replace {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -52,7 +52,7 @@ mod tests {
             .unwrap();
         let error = Replace::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in replace element.");
@@ -65,7 +65,7 @@ mod tests {
             .unwrap();
         let error = Replace::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in replace element.");
@@ -78,7 +78,7 @@ mod tests {
             .unwrap();
         let error = Replace::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'id' missing.");

parsers/src/muc/muc.rs ๐Ÿ”—

@@ -94,9 +94,9 @@ impl Muc {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
     use std::str::FromStr;
+    use xso::error::{Error, FromElementError};
 
     #[test]
     fn test_muc_simple() {
@@ -113,7 +113,7 @@ mod tests {
             .unwrap();
         let error = Muc::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in x element.");
@@ -140,7 +140,7 @@ mod tests {
             .unwrap();
         let error = Muc::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in x element.");

parsers/src/muc/user.rs ๐Ÿ”—

@@ -8,8 +8,8 @@
 use crate::message::MessagePayload;
 use crate::ns;
 use crate::presence::PresencePayload;
-use crate::util::error::Error;
 use crate::Element;
+use xso::error::{Error, FromElementError};
 
 use jid::FullJid;
 
@@ -94,9 +94,9 @@ pub enum Actor {
 }
 
 impl TryFrom<Element> for Actor {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Actor, Error> {
+    fn try_from(elem: Element) -> Result<Actor, FromElementError> {
         check_self!(elem, "actor", MUC_USER);
         check_no_unknown_attributes!(elem, "actor", ["jid", "nick"]);
         check_no_children!(elem, "actor");
@@ -104,9 +104,9 @@ impl TryFrom<Element> for Actor {
         let nick = get_attr!(elem, "nick", Option);
 
         match (jid, nick) {
-            (Some(_), Some(_)) | (None, None) => Err(Error::ParseError(
-                "Either 'jid' or 'nick' attribute is required.",
-            )),
+            (Some(_), Some(_)) | (None, None) => {
+                Err(Error::Other("Either 'jid' or 'nick' attribute is required.").into())
+            }
             (Some(jid), _) => Ok(Actor::Jid(jid)),
             (_, Some(nick)) => Ok(Actor::Nick(nick)),
         }
@@ -343,7 +343,7 @@ mod tests {
             .unwrap();
         let error = MucUser::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in x element.");
@@ -370,7 +370,7 @@ mod tests {
             .unwrap();
         let error = MucUser::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in x element.");
@@ -391,7 +391,7 @@ mod tests {
             .unwrap();
         let error = Status::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'code' missing.");
@@ -407,7 +407,7 @@ mod tests {
             .unwrap();
         let error = Status::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in status element.");
@@ -429,7 +429,7 @@ mod tests {
             .unwrap();
         let error = Status::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Invalid status code value.");
@@ -442,7 +442,11 @@ mod tests {
             .unwrap();
         let error = Status::try_from(elem).unwrap_err();
         let error = match error {
-            Error::ParseIntError(error) => error,
+            FromElementError::Invalid(Error::TextParseError(error))
+                if error.is::<std::num::ParseIntError>() =>
+            {
+                error
+            }
             _ => panic!(),
         };
         assert_eq!(error.to_string(), "invalid digit found in string");
@@ -455,7 +459,7 @@ mod tests {
             .unwrap();
         let error = Actor::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
@@ -470,7 +474,7 @@ mod tests {
             .unwrap();
         let error = Actor::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
@@ -530,7 +534,7 @@ mod tests {
             .unwrap();
         let continue_ = Continue::try_from(elem).unwrap_err();
         let message = match continue_ {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in continue element.".to_owned());
@@ -557,7 +561,7 @@ mod tests {
             .unwrap();
         let error = Reason::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in reason element.".to_owned());
@@ -573,7 +577,7 @@ mod tests {
             .unwrap();
         let error = Reason::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in reason element.".to_owned());
@@ -588,7 +592,7 @@ mod tests {
             .unwrap();
         let error = Item::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in item element.".to_owned());
@@ -612,7 +616,7 @@ mod tests {
             .unwrap();
         let error = Item::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'role' missing.".to_owned());
@@ -640,7 +644,7 @@ mod tests {
             .unwrap();
         let error = Item::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(

parsers/src/nick.rs ๐Ÿ”—

@@ -14,9 +14,9 @@ generate_elem_id!(
 #[cfg(test)]
 mod tests {
     use super::*;
-    #[cfg(not(feature = "disable-validation"))]
-    use crate::util::error::Error;
     use crate::Element;
+    #[cfg(not(feature = "disable-validation"))]
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -56,7 +56,7 @@ mod tests {
             .unwrap();
         let error = Nick::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in nick element.");
@@ -70,7 +70,7 @@ mod tests {
             .unwrap();
         let error = Nick::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in nick element.");

parsers/src/occupant_id.rs ๐Ÿ”—

@@ -26,8 +26,8 @@ impl PresencePayload for OccupantId {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -57,7 +57,7 @@ mod tests {
             .unwrap();
         let error = OccupantId::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in occupant-id element.");
@@ -70,7 +70,7 @@ mod tests {
             .unwrap();
         let error = OccupantId::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'id' missing.");

parsers/src/oob.rs ๐Ÿ”—

@@ -22,8 +22,8 @@ impl MessagePayload for Oob {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -59,7 +59,7 @@ mod tests {
         let elem: Element = "<x xmlns='jabber:x:oob'></x>".parse().unwrap();
         let error = Oob::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Missing child url in x element.");

parsers/src/ping.rs ๐Ÿ”—

@@ -20,9 +20,9 @@ impl IqGetPayload for Ping {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    #[cfg(not(feature = "disable-validation"))]
-    use crate::util::error::Error;
     use crate::Element;
+    #[cfg(not(feature = "disable-validation"))]
+    use xso::error::{Error, FromElementError};
 
     #[test]
     fn test_size() {
@@ -50,7 +50,7 @@ mod tests {
             .unwrap();
         let error = Ping::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in ping element.");
@@ -62,7 +62,7 @@ mod tests {
         let elem: Element = "<ping xmlns='urn:xmpp:ping' coucou=''/>".parse().unwrap();
         let error = Ping::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in ping element.");

parsers/src/presence.rs ๐Ÿ”—

@@ -6,11 +6,11 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use crate::ns;
-use crate::util::error::Error;
 use jid::Jid;
 use minidom::{Element, IntoAttributeValue};
 use std::collections::BTreeMap;
 use std::str::FromStr;
+use xso::error::{Error, FromElementError};
 
 /// Should be implemented on every known payload of a `<presence/>`.
 pub trait PresencePayload: TryFrom<Element> + Into<Element> {}
@@ -42,7 +42,7 @@ impl FromStr for Show {
             "dnd" => Show::Dnd,
             "xa" => Show::Xa,
 
-            _ => return Err(Error::ParseError("Invalid value for show.")),
+            _ => return Err(Error::Other("Invalid value for show.").into()),
         })
     }
 }
@@ -114,9 +114,7 @@ impl FromStr for Type {
             "unsubscribed" => Type::Unsubscribed,
 
             _ => {
-                return Err(Error::ParseError(
-                    "Invalid 'type' attribute on presence element.",
-                ));
+                return Err(Error::Other("Invalid 'type' attribute on presence element.").into());
             }
         })
     }
@@ -281,9 +279,9 @@ impl Presence {
 }
 
 impl TryFrom<Element> for Presence {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(root: Element) -> Result<Presence, Error> {
+    fn try_from(root: Element) -> Result<Presence, FromElementError> {
         check_self!(root, "presence", DEFAULT_NS);
         let mut show = None;
         let mut priority = None;
@@ -300,9 +298,7 @@ impl TryFrom<Element> for Presence {
         for elem in root.children() {
             if elem.is("show", ns::DEFAULT_NS) {
                 if show.is_some() {
-                    return Err(Error::ParseError(
-                        "More than one show element in a presence.",
-                    ));
+                    return Err(Error::Other("More than one show element in a presence.").into());
                 }
                 check_no_attributes!(elem, "show");
                 check_no_children!(elem, "show");
@@ -312,19 +308,22 @@ impl TryFrom<Element> for Presence {
                 check_no_children!(elem, "status");
                 let lang = get_attr!(elem, "xml:lang", Default);
                 if presence.statuses.insert(lang, elem.text()).is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Status element present twice for the same xml:lang.",
-                    ));
+                    )
+                    .into());
                 }
             } else if elem.is("priority", ns::DEFAULT_NS) {
                 if priority.is_some() {
-                    return Err(Error::ParseError(
-                        "More than one priority element in a presence.",
-                    ));
+                    return Err(
+                        Error::Other("More than one priority element in a presence.").into(),
+                    );
                 }
                 check_no_attributes!(elem, "priority");
                 check_no_children!(elem, "priority");
-                priority = Some(Priority::from_str(elem.text().as_ref())?);
+                priority = Some(
+                    Priority::from_str(elem.text().as_ref()).map_err(Error::text_parse_error)?,
+                );
             } else {
                 presence.payloads.push(elem.clone());
             }
@@ -461,7 +460,7 @@ mod tests {
             .unwrap();
         let error = Presence::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Invalid value for show.");
@@ -481,7 +480,7 @@ mod tests {
                 .unwrap();
         let error = Presence::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Invalid value for show.");
@@ -541,7 +540,7 @@ mod tests {
         let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>Lร !</status></presence>".parse().unwrap();
         let error = Presence::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(
@@ -579,7 +578,11 @@ mod tests {
                 .unwrap();
         let error = Presence::try_from(elem).unwrap_err();
         match error {
-            Error::ParseIntError(_) => (),
+            FromElementError::Invalid(Error::TextParseError(e))
+                if e.is::<std::num::ParseIntError>() =>
+            {
+                ()
+            }
             _ => panic!(),
         };
     }
@@ -614,7 +617,7 @@ mod tests {
                 .unwrap();
         let error = Presence::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in status element.");
@@ -634,7 +637,7 @@ mod tests {
                 .unwrap();
         let error = Presence::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in status element.");

parsers/src/pubsub/event.rs ๐Ÿ”—

@@ -9,9 +9,9 @@ use crate::date::DateTime;
 use crate::message::MessagePayload;
 use crate::ns;
 use crate::pubsub::{Item as PubSubItem, ItemId, NodeName, Subscription, SubscriptionId};
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
+use xso::error::{Error, FromElementError};
 
 /// Event wrapper for a PubSub `<item/>`.
 #[derive(Debug, Clone, PartialEq)]
@@ -97,9 +97,7 @@ fn parse_items(elem: Element, node: NodeName) -> Result<PubSubEvent, Error> {
                 None => is_retract = Some(false),
                 Some(false) => (),
                 Some(true) => {
-                    return Err(Error::ParseError(
-                        "Mix of item and retract in items element.",
-                    ));
+                    return Err(Error::Other("Mix of item and retract in items element.").into());
                 }
             }
             items.push(Item::try_from(child.clone())?);
@@ -108,9 +106,7 @@ fn parse_items(elem: Element, node: NodeName) -> Result<PubSubEvent, Error> {
                 None => is_retract = Some(true),
                 Some(true) => (),
                 Some(false) => {
-                    return Err(Error::ParseError(
-                        "Mix of item and retract in items element.",
-                    ));
+                    return Err(Error::Other("Mix of item and retract in items element.").into());
                 }
             }
             check_no_children!(child, "retract");
@@ -118,7 +114,7 @@ fn parse_items(elem: Element, node: NodeName) -> Result<PubSubEvent, Error> {
             let id = get_attr!(child, "id", Required);
             retracts.push(id);
         } else {
-            return Err(Error::ParseError("Invalid child in items element."));
+            return Err(Error::Other("Invalid child in items element.").into());
         }
     }
     Ok(match is_retract {
@@ -127,14 +123,14 @@ fn parse_items(elem: Element, node: NodeName) -> Result<PubSubEvent, Error> {
             node,
             items: retracts,
         },
-        None => return Err(Error::ParseError("Missing children in items element.")),
+        None => return Err(Error::Other("Missing children in items element.").into()),
     })
 }
 
 impl TryFrom<Element> for PubSubEvent {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<PubSubEvent, Error> {
+    fn try_from(elem: Element) -> Result<PubSubEvent, FromElementError> {
         check_self!(elem, "event", PUBSUB_EVENT);
         check_no_attributes!(elem, "event");
 
@@ -145,9 +141,10 @@ impl TryFrom<Element> for PubSubEvent {
                 let mut payloads = child.children().cloned().collect::<Vec<_>>();
                 let item = payloads.pop();
                 if !payloads.is_empty() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "More than a single payload in configuration element.",
-                    ));
+                    )
+                    .into());
                 }
                 let form = match item {
                     None => None,
@@ -159,14 +156,14 @@ impl TryFrom<Element> for PubSubEvent {
                 for item in child.children() {
                     if item.is("redirect", ns::PUBSUB_EVENT) {
                         if redirect.is_some() {
-                            return Err(Error::ParseError(
-                                "More than one redirect in delete element.",
-                            ));
+                            return Err(
+                                Error::Other("More than one redirect in delete element.").into()
+                            );
                         }
                         let uri = get_attr!(item, "uri", Required);
                         redirect = Some(uri);
                     } else {
-                        return Err(Error::ParseError("Unknown child in delete element."));
+                        return Err(Error::Other("Unknown child in delete element.").into());
                     }
                 }
                 payload = Some(PubSubEvent::Delete { node, redirect });
@@ -185,10 +182,10 @@ impl TryFrom<Element> for PubSubEvent {
                     subscription: get_attr!(child, "subscription", Option),
                 });
             } else {
-                return Err(Error::ParseError("Unknown child in event element."));
+                return Err(Error::Other("Unknown child in event element.").into());
             }
         }
-        payload.ok_or(Error::ParseError("No payload in event element."))
+        payload.ok_or(Error::Other("No payload in event element.").into())
     }
 }
 
@@ -270,7 +267,7 @@ mod tests {
                 .unwrap();
         let error = PubSubEvent::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Missing children in items element.");
@@ -375,7 +372,7 @@ mod tests {
                 .unwrap();
         let error = PubSubEvent::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in event element.");
@@ -389,7 +386,7 @@ mod tests {
             .unwrap();
         let error = PubSubEvent::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in event element.");

parsers/src/pubsub/owner.rs ๐Ÿ”—

@@ -9,9 +9,9 @@ use crate::data_forms::DataForm;
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::ns;
 use crate::pubsub::{AffiliationAttribute, NodeName, Subscription};
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
+use xso::error::{Error, FromElementError};
 
 generate_element!(
     /// A list of affiliations you have on a service, or on a node.
@@ -143,9 +143,9 @@ impl IqSetPayload for PubSubOwner {}
 impl IqResultPayload for PubSubOwner {}
 
 impl TryFrom<Element> for PubSubOwner {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<PubSubOwner, Error> {
+    fn try_from(elem: Element) -> Result<PubSubOwner, FromElementError> {
         check_self!(elem, "pubsub", PUBSUB_OWNER);
         check_no_attributes!(elem, "pubsub");
 
@@ -153,17 +153,18 @@ impl TryFrom<Element> for PubSubOwner {
         for child in elem.children() {
             if child.is("configure", ns::PUBSUB_OWNER) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Payload is already defined in pubsub owner element.",
-                    ));
+                    )
+                    .into());
                 }
                 let configure = Configure::try_from(child.clone())?;
                 payload = Some(PubSubOwner::Configure(configure));
             } else {
-                return Err(Error::ParseError("Unknown child in pubsub element."));
+                return Err(Error::Other("Unknown child in pubsub element.").into());
             }
         }
-        payload.ok_or(Error::ParseError("No payload in pubsub element."))
+        payload.ok_or(Error::Other("No payload in pubsub element.").into())
     }
 }
 

parsers/src/pubsub/pubsub.rs ๐Ÿ”—

@@ -10,9 +10,9 @@ use crate::ns;
 use crate::pubsub::{
     AffiliationAttribute, Item as PubSubItem, NodeName, Subscription, SubscriptionId,
 };
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
+use xso::error::{Error, FromElementError};
 
 // TODO: a better solution would be to split this into a query and a result elements, like for
 // XEP-0030.
@@ -181,24 +181,23 @@ pub struct SubscribeOptions {
 }
 
 impl TryFrom<Element> for SubscribeOptions {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Self, Error> {
+    fn try_from(elem: Element) -> Result<Self, FromElementError> {
         check_self!(elem, "subscribe-options", PUBSUB);
         check_no_attributes!(elem, "subscribe-options");
         let mut required = false;
         for child in elem.children() {
             if child.is("required", ns::PUBSUB) {
                 if required {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "More than one required element in subscribe-options.",
-                    ));
+                    )
+                    .into());
                 }
                 required = true;
             } else {
-                return Err(Error::ParseError(
-                    "Unknown child in subscribe-options element.",
-                ));
+                return Err(Error::Other("Unknown child in subscribe-options element.").into());
             }
         }
         Ok(SubscribeOptions { required })
@@ -338,9 +337,9 @@ impl IqSetPayload for PubSub {}
 impl IqResultPayload for PubSub {}
 
 impl TryFrom<Element> for PubSub {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<PubSub, Error> {
+    fn try_from(elem: Element) -> Result<PubSub, FromElementError> {
         check_self!(elem, "pubsub", PUBSUB);
         check_no_attributes!(elem, "pubsub");
 
@@ -348,9 +347,9 @@ impl TryFrom<Element> for PubSub {
         for child in elem.children() {
             if child.is("create", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let create = Create::try_from(child.clone())?;
                 payload = Some(PubSub::Create {
@@ -359,9 +358,9 @@ impl TryFrom<Element> for PubSub {
                 });
             } else if child.is("subscribe", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let subscribe = Subscribe::try_from(child.clone())?;
                 payload = Some(PubSub::Subscribe {
@@ -371,9 +370,9 @@ impl TryFrom<Element> for PubSub {
             } else if child.is("options", ns::PUBSUB) {
                 if let Some(PubSub::Subscribe { subscribe, options }) = payload {
                     if options.is_some() {
-                        return Err(Error::ParseError(
-                            "Options is already defined in pubsub element.",
-                        ));
+                        return Err(
+                            Error::Other("Options is already defined in pubsub element.").into(),
+                        );
                     }
                     let options = Some(Options::try_from(child.clone())?);
                     payload = Some(PubSub::Subscribe { subscribe, options });
@@ -384,29 +383,30 @@ impl TryFrom<Element> for PubSub {
                         options: Some(options),
                     });
                 } else {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
             } else if child.is("configure", ns::PUBSUB) {
                 if let Some(PubSub::Create { create, configure }) = payload {
                     if configure.is_some() {
-                        return Err(Error::ParseError(
+                        return Err(Error::Other(
                             "Configure is already defined in pubsub element.",
-                        ));
+                        )
+                        .into());
                     }
                     let configure = Some(Configure::try_from(child.clone())?);
                     payload = Some(PubSub::Create { create, configure });
                 } else {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
             } else if child.is("publish", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let publish = Publish::try_from(child.clone())?;
                 payload = Some(PubSub::Publish {
@@ -420,9 +420,10 @@ impl TryFrom<Element> for PubSub {
                 }) = payload
                 {
                     if publish_options.is_some() {
-                        return Err(Error::ParseError(
+                        return Err(Error::Other(
                             "Publish-options are already defined in pubsub element.",
-                        ));
+                        )
+                        .into());
                     }
                     let publish_options = Some(PublishOptions::try_from(child.clone())?);
                     payload = Some(PubSub::Publish {
@@ -430,71 +431,71 @@ impl TryFrom<Element> for PubSub {
                         publish_options,
                     });
                 } else {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
             } else if child.is("affiliations", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let affiliations = Affiliations::try_from(child.clone())?;
                 payload = Some(PubSub::Affiliations(affiliations));
             } else if child.is("default", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let default = Default::try_from(child.clone())?;
                 payload = Some(PubSub::Default(default));
             } else if child.is("items", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let items = Items::try_from(child.clone())?;
                 payload = Some(PubSub::Items(items));
             } else if child.is("retract", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let retract = Retract::try_from(child.clone())?;
                 payload = Some(PubSub::Retract(retract));
             } else if child.is("subscription", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let subscription = SubscriptionElem::try_from(child.clone())?;
                 payload = Some(PubSub::Subscription(subscription));
             } else if child.is("subscriptions", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let subscriptions = Subscriptions::try_from(child.clone())?;
                 payload = Some(PubSub::Subscriptions(subscriptions));
             } else if child.is("unsubscribe", ns::PUBSUB) {
                 if payload.is_some() {
-                    return Err(Error::ParseError(
-                        "Payload is already defined in pubsub element.",
-                    ));
+                    return Err(
+                        Error::Other("Payload is already defined in pubsub element.").into(),
+                    );
                 }
                 let unsubscribe = Unsubscribe::try_from(child.clone())?;
                 payload = Some(PubSub::Unsubscribe(unsubscribe));
             } else {
-                return Err(Error::ParseError("Unknown child in pubsub element."));
+                return Err(Error::Other("Unknown child in pubsub element.").into());
             }
         }
-        payload.ok_or(Error::ParseError("No payload in pubsub element."))
+        payload.ok_or(Error::Other("No payload in pubsub element.").into())
     }
 }
 
@@ -678,7 +679,7 @@ mod tests {
             .unwrap();
         let error = PubSub::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "No payload in pubsub element.");

parsers/src/receipts.rs ๐Ÿ”—

@@ -32,8 +32,8 @@ impl MessagePayload for Received {}
 mod tests {
     use super::*;
     use crate::ns;
-    use crate::util::error::Error;
     use crate::Element;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -65,7 +65,7 @@ mod tests {
         let elem: Element = "<received xmlns='urn:xmpp:receipts'/>".parse().unwrap();
         let error = Received::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'id' missing.");

parsers/src/roster.rs ๐Ÿ”—

@@ -91,9 +91,9 @@ impl IqResultPayload for Roster {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
     use std::str::FromStr;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -270,7 +270,7 @@ mod tests {
             .unwrap();
         let error = Roster::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in query element.");
@@ -280,7 +280,7 @@ mod tests {
             .unwrap();
         let error = Roster::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown attribute in query element.");
@@ -293,7 +293,7 @@ mod tests {
             .unwrap();
         let error = Roster::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'jid' missing.");
@@ -314,7 +314,7 @@ mod tests {
                 .unwrap();
         let error = Roster::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in item element.");

parsers/src/rsm.rs ๐Ÿ”—

@@ -5,8 +5,8 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
+use xso::error::{Error, FromElementError};
 
 /// Requests paging through a potentially big set of items (represented by an
 /// UID).
@@ -28,9 +28,9 @@ pub struct SetQuery {
 }
 
 impl TryFrom<Element> for SetQuery {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<SetQuery, Error> {
+    fn try_from(elem: Element) -> Result<SetQuery, FromElementError> {
         check_self!(elem, "set", RSM, "RSM set");
         let mut set = SetQuery {
             max: None,
@@ -41,26 +41,26 @@ impl TryFrom<Element> for SetQuery {
         for child in elem.children() {
             if child.is("max", ns::RSM) {
                 if set.max.is_some() {
-                    return Err(Error::ParseError("Set canโ€™t have more than one max."));
+                    return Err(Error::Other("Set canโ€™t have more than one max.").into());
                 }
-                set.max = Some(child.text().parse()?);
+                set.max = Some(child.text().parse().map_err(Error::text_parse_error)?);
             } else if child.is("after", ns::RSM) {
                 if set.after.is_some() {
-                    return Err(Error::ParseError("Set canโ€™t have more than one after."));
+                    return Err(Error::Other("Set canโ€™t have more than one after.").into());
                 }
                 set.after = Some(child.text());
             } else if child.is("before", ns::RSM) {
                 if set.before.is_some() {
-                    return Err(Error::ParseError("Set canโ€™t have more than one before."));
+                    return Err(Error::Other("Set canโ€™t have more than one before.").into());
                 }
                 set.before = Some(child.text());
             } else if child.is("index", ns::RSM) {
                 if set.index.is_some() {
-                    return Err(Error::ParseError("Set canโ€™t have more than one index."));
+                    return Err(Error::Other("Set canโ€™t have more than one index.").into());
                 }
-                set.index = Some(child.text().parse()?);
+                set.index = Some(child.text().parse().map_err(Error::text_parse_error)?);
             } else {
-                return Err(Error::ParseError("Unknown child in set element."));
+                return Err(Error::Other("Unknown child in set element.").into());
             }
         }
         Ok(set)
@@ -111,9 +111,9 @@ pub struct SetResult {
 }
 
 impl TryFrom<Element> for SetResult {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<SetResult, Error> {
+    fn try_from(elem: Element) -> Result<SetResult, FromElementError> {
         check_self!(elem, "set", RSM, "RSM set");
         let mut set = SetResult {
             first: None,
@@ -124,22 +124,22 @@ impl TryFrom<Element> for SetResult {
         for child in elem.children() {
             if child.is("first", ns::RSM) {
                 if set.first.is_some() {
-                    return Err(Error::ParseError("Set canโ€™t have more than one first."));
+                    return Err(Error::Other("Set canโ€™t have more than one first.").into());
                 }
                 set.first_index = get_attr!(child, "index", Option);
                 set.first = Some(child.text());
             } else if child.is("last", ns::RSM) {
                 if set.last.is_some() {
-                    return Err(Error::ParseError("Set canโ€™t have more than one last."));
+                    return Err(Error::Other("Set canโ€™t have more than one last.").into());
                 }
                 set.last = Some(child.text());
             } else if child.is("count", ns::RSM) {
                 if set.count.is_some() {
-                    return Err(Error::ParseError("Set canโ€™t have more than one count."));
+                    return Err(Error::Other("Set canโ€™t have more than one count.").into());
                 }
-                set.count = Some(child.text().parse()?);
+                set.count = Some(child.text().parse().map_err(Error::text_parse_error)?);
             } else {
-                return Err(Error::ParseError("Unknown child in set element."));
+                return Err(Error::Other("Unknown child in set element.").into());
             }
         }
         Ok(set)
@@ -215,7 +215,7 @@ mod tests {
             .unwrap();
         let error = SetQuery::try_from(elem.clone()).unwrap_err();
         let returned_elem = match error {
-            Error::TypeMismatch(_, _, elem) => elem,
+            FromElementError::Mismatch(elem) => elem,
             _ => panic!(),
         };
         assert_eq!(elem, returned_elem);
@@ -225,7 +225,7 @@ mod tests {
             .unwrap();
         let error = SetResult::try_from(elem.clone()).unwrap_err();
         let returned_elem = match error {
-            Error::TypeMismatch(_, _, elem) => elem,
+            FromElementError::Mismatch(elem) => elem,
             _ => panic!(),
         };
         assert_eq!(elem, returned_elem);
@@ -238,7 +238,7 @@ mod tests {
             .unwrap();
         let error = SetQuery::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in set element.");
@@ -248,7 +248,7 @@ mod tests {
             .unwrap();
         let error = SetResult::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in set element.");

parsers/src/rtt.rs ๐Ÿ”—

@@ -5,9 +5,9 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use crate::ns;
-use crate::util::error::Error;
 use crate::util::text_node_codecs::{Codec, OptionalCodec, Text};
 use crate::Element;
+use xso::error::{Error, FromElementError};
 
 generate_attribute!(
     /// Events for real-time text.
@@ -49,7 +49,7 @@ impl TryFrom<Action> for Insert {
     fn try_from(action: Action) -> Result<Insert, Error> {
         match action {
             Action::Insert(insert) => Ok(insert),
-            _ => Err(Error::ParseError("This is not an insert action.")),
+            _ => Err(Error::Other("This is not an insert action.")),
         }
     }
 }
@@ -79,8 +79,8 @@ pub struct Erase {
 }
 
 impl TryFrom<Element> for Erase {
-    type Error = Error;
-    fn try_from(elem: Element) -> Result<Erase, Error> {
+    type Error = FromElementError;
+    fn try_from(elem: Element) -> Result<Erase, FromElementError> {
         check_self!(elem, "e", RTT);
         check_no_unknown_attributes!(elem, "e", ["p", "n"]);
         let pos = get_attr!(elem, "p", Option);
@@ -105,7 +105,7 @@ impl TryFrom<Action> for Erase {
     fn try_from(action: Action) -> Result<Erase, Error> {
         match action {
             Action::Erase(erase) => Ok(erase),
-            _ => Err(Error::ParseError("This is not an erase action.")),
+            _ => Err(Error::Other("This is not an erase action.")),
         }
     }
 }
@@ -127,7 +127,7 @@ impl TryFrom<Action> for Wait {
     fn try_from(action: Action) -> Result<Wait, Error> {
         match action {
             Action::Wait(wait) => Ok(wait),
-            _ => Err(Error::ParseError("This is not a wait action.")),
+            _ => Err(Error::Other("This is not a wait action.")),
         }
     }
 }
@@ -146,14 +146,14 @@ pub enum Action {
 }
 
 impl TryFrom<Element> for Action {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Action, Error> {
+    fn try_from(elem: Element) -> Result<Action, FromElementError> {
         match elem.name() {
             "t" => Insert::try_from(elem).map(Action::Insert),
             "e" => Erase::try_from(elem).map(Action::Erase),
             "w" => Wait::try_from(elem).map(Action::Wait),
-            _ => Err(Error::ParseError("This is not a rtt action element.")),
+            _ => Err(FromElementError::Mismatch(elem)),
         }
     }
 }
@@ -204,8 +204,8 @@ pub struct Rtt {
 }
 
 impl TryFrom<Element> for Rtt {
-    type Error = Error;
-    fn try_from(elem: Element) -> Result<Rtt, Error> {
+    type Error = FromElementError;
+    fn try_from(elem: Element) -> Result<Rtt, FromElementError> {
         check_self!(elem, "rtt", RTT);
 
         check_no_unknown_attributes!(elem, "rtt", ["seq", "event", "id"]);
@@ -216,7 +216,7 @@ impl TryFrom<Element> for Rtt {
         let mut actions = Vec::new();
         for child in elem.children() {
             if child.ns() != ns::RTT {
-                return Err(Error::ParseError("Unknown child in rtt element."));
+                return Err(Error::Other("Unknown child in rtt element.").into());
             }
             actions.push(Action::try_from(child.clone())?);
         }

parsers/src/sasl.rs ๐Ÿ”—

@@ -5,10 +5,10 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 use crate::ns;
-use crate::util::error::Error;
 use crate::util::text_node_codecs::{Base64, Codec};
 use crate::Element;
 use std::collections::BTreeMap;
+use xso::error::{Error, FromElementError};
 
 generate_attribute!(
     /// The list of available SASL mechanisms.
@@ -150,9 +150,9 @@ pub struct Failure {
 }
 
 impl TryFrom<Element> for Failure {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(root: Element) -> Result<Failure, Error> {
+    fn try_from(root: Element) -> Result<Failure, FromElementError> {
         check_self!(root, "failure", SASL);
         check_no_attributes!(root, "failure");
 
@@ -165,15 +165,17 @@ impl TryFrom<Element> for Failure {
                 check_no_children!(child, "text");
                 let lang = get_attr!(child, "xml:lang", Default);
                 if texts.insert(lang, child.text()).is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Text element present twice for the same xml:lang in failure element.",
-                    ));
+                    )
+                    .into());
                 }
             } else if child.has_ns(ns::SASL) {
                 if defined_condition.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Failure must not have more than one defined-condition.",
-                    ));
+                    )
+                    .into());
                 }
                 check_no_attributes!(child, "defined-condition");
                 check_no_children!(child, "defined-condition");
@@ -184,11 +186,11 @@ impl TryFrom<Element> for Failure {
                 };
                 defined_condition = Some(condition);
             } else {
-                return Err(Error::ParseError("Unknown element in Failure."));
+                return Err(Error::Other("Unknown element in Failure.").into());
             }
         }
         let defined_condition =
-            defined_condition.ok_or(Error::ParseError("Failure must have a defined-condition."))?;
+            defined_condition.ok_or(Error::Other("Failure must have a defined-condition."))?;
 
         Ok(Failure {
             defined_condition,

parsers/src/server_info.rs ๐Ÿ”—

@@ -5,7 +5,7 @@
 
 use crate::data_forms::{DataForm, DataFormType, Field, FieldType};
 use crate::ns;
-use crate::util::error::Error;
+use xso::error::Error;
 
 /// Structure representing a `http://jabber.org/network/serverinfo` form type.
 #[derive(Debug, Clone, PartialEq, Default)]
@@ -34,15 +34,15 @@ impl TryFrom<DataForm> for ServerInfo {
 
     fn try_from(form: DataForm) -> Result<ServerInfo, Error> {
         if form.type_ != DataFormType::Result_ {
-            return Err(Error::ParseError("Wrong type of form."));
+            return Err(Error::Other("Wrong type of form."));
         }
         if form.form_type != Some(String::from(ns::SERVER_INFO)) {
-            return Err(Error::ParseError("Wrong FORM_TYPE for form."));
+            return Err(Error::Other("Wrong FORM_TYPE for form."));
         }
         let mut server_info = ServerInfo::default();
         for field in form.fields {
             if field.type_ != FieldType::ListMulti {
-                return Err(Error::ParseError("Field is not of the required type."));
+                return Err(Error::Other("Field is not of the required type."));
             }
             if field.var.as_deref() == Some("abuse-addresses") {
                 server_info.abuse = field.values;
@@ -57,7 +57,7 @@ impl TryFrom<DataForm> for ServerInfo {
             } else if field.var.as_deref() == Some("support-addresses") {
                 server_info.support = field.values;
             } else {
-                return Err(Error::ParseError("Unknown form field var."));
+                return Err(Error::Other("Unknown form field var."));
             }
         }
 

parsers/src/stanza_error.rs ๐Ÿ”—

@@ -7,12 +7,12 @@
 use crate::message::MessagePayload;
 use crate::ns;
 use crate::presence::PresencePayload;
-use crate::util::error::Error;
 use crate::Element;
 use jid::Jid;
 use minidom::Node;
 use std::collections::BTreeMap;
 use std::convert::TryFrom;
+use xso::error::{Error, FromElementError};
 
 generate_attribute!(
     /// The type of the error.
@@ -249,9 +249,9 @@ impl StanzaError {
 }
 
 impl TryFrom<Element> for StanzaError {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<StanzaError, Error> {
+    fn try_from(elem: Element) -> Result<StanzaError, FromElementError> {
         check_self!(elem, "error", DEFAULT_NS);
         // The code attribute has been deprecated in [XEP-0086](https://xmpp.org/extensions/xep-0086.html)
         // which was deprecated in 2007. We don't error when it's here, but don't include it in the final struct.
@@ -273,15 +273,16 @@ impl TryFrom<Element> for StanzaError {
                 check_no_unknown_attributes!(child, "text", ["xml:lang"]);
                 let lang = get_attr!(child, "xml:lang", Default);
                 if stanza_error.texts.insert(lang, child.text()).is_some() {
-                    return Err(Error::ParseError(
-                        "Text element present twice for the same xml:lang.",
-                    ));
+                    return Err(
+                        Error::Other("Text element present twice for the same xml:lang.").into(),
+                    );
                 }
             } else if child.has_ns(ns::XMPP_STANZAS) {
                 if defined_condition.is_some() {
-                    return Err(Error::ParseError(
+                    return Err(Error::Other(
                         "Error must not have more than one defined-condition.",
-                    ));
+                    )
+                    .into());
                 }
                 check_no_children!(child, "defined-condition");
                 check_no_attributes!(child, "defined-condition");
@@ -297,15 +298,15 @@ impl TryFrom<Element> for StanzaError {
                 defined_condition = Some(condition);
             } else {
                 if stanza_error.other.is_some() {
-                    return Err(Error::ParseError(
-                        "Error must not have more than one other element.",
-                    ));
+                    return Err(
+                        Error::Other("Error must not have more than one other element.").into(),
+                    );
                 }
                 stanza_error.other = Some(child.clone());
             }
         }
         stanza_error.defined_condition =
-            defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
+            defined_condition.ok_or(Error::Other("Error must have a defined-condition."))?;
 
         Ok(stanza_error)
     }
@@ -369,7 +370,7 @@ mod tests {
         let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
         let error = StanzaError::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'type' missing.");
@@ -384,10 +385,10 @@ mod tests {
             .unwrap();
         let error = StanzaError::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::TextParseError(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'type' attribute.");
+        assert_eq!(message.to_string(), "Unknown value for 'type' attribute.");
     }
 
     #[test]
@@ -402,7 +403,7 @@ mod tests {
             .unwrap();
         let error = StanzaError::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Error must have a defined-condition.");

parsers/src/stanza_id.rs ๐Ÿ”—

@@ -37,9 +37,9 @@ impl MessagePayload for OriginId {}
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::util::error::Error;
     use crate::Element;
     use jid::BareJid;
+    use xso::error::{Error, FromElementError};
 
     #[cfg(target_pointer_width = "32")]
     #[test]
@@ -78,7 +78,7 @@ mod tests {
             .unwrap();
         let error = StanzaId::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Unknown child in stanza-id element.");
@@ -89,7 +89,7 @@ mod tests {
         let elem: Element = "<stanza-id xmlns='urn:xmpp:sid:0'/>".parse().unwrap();
         let error = StanzaId::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'id' missing.");
@@ -102,7 +102,7 @@ mod tests {
             .unwrap();
         let error = StanzaId::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Required attribute 'by' missing.");

parsers/src/time.rs ๐Ÿ”—

@@ -7,10 +7,10 @@
 use crate::date::DateTime;
 use crate::iq::{IqGetPayload, IqResultPayload};
 use crate::ns;
-use crate::util::error::Error;
 use crate::Element;
 use chrono::FixedOffset;
 use std::str::FromStr;
+use xso::error::{Error, FromElementError};
 
 generate_empty_element!(
     /// An entity time query.
@@ -28,9 +28,9 @@ pub struct TimeResult(pub DateTime);
 impl IqResultPayload for TimeResult {}
 
 impl TryFrom<Element> for TimeResult {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<TimeResult, Error> {
+    fn try_from(elem: Element) -> Result<TimeResult, FromElementError> {
         check_self!(elem, "time", TIME);
         check_no_attributes!(elem, "time");
 
@@ -40,33 +40,34 @@ impl TryFrom<Element> for TimeResult {
         for child in elem.children() {
             if child.is("tzo", ns::TIME) {
                 if tzo.is_some() {
-                    return Err(Error::ParseError("More than one tzo element in time."));
+                    return Err(Error::Other("More than one tzo element in time.").into());
                 }
                 check_no_children!(child, "tzo");
                 check_no_attributes!(child, "tzo");
                 // TODO: Add a FromStr implementation to FixedOffset to avoid this hack.
                 let fake_date = format!("{}{}", "2019-04-22T11:38:00", child.text());
-                let date_time = DateTime::from_str(&fake_date)?;
+                let date_time = DateTime::from_str(&fake_date).map_err(Error::text_parse_error)?;
                 tzo = Some(date_time.timezone());
             } else if child.is("utc", ns::TIME) {
                 if utc.is_some() {
-                    return Err(Error::ParseError("More than one utc element in time."));
+                    return Err(Error::Other("More than one utc element in time.").into());
                 }
                 check_no_children!(child, "utc");
                 check_no_attributes!(child, "utc");
-                let date_time = DateTime::from_str(&child.text())?;
+                let date_time =
+                    DateTime::from_str(&child.text()).map_err(Error::text_parse_error)?;
                 match FixedOffset::east_opt(0) {
                     Some(tz) if date_time.timezone() == tz => (),
-                    _ => return Err(Error::ParseError("Non-UTC timezone for utc element.")),
+                    _ => return Err(Error::Other("Non-UTC timezone for utc element.").into()),
                 }
                 utc = Some(date_time);
             } else {
-                return Err(Error::ParseError("Unknown child in time element."));
+                return Err(Error::Other("Unknown child in time element.").into());
             }
         }
 
-        let tzo = tzo.ok_or(Error::ParseError("Missing tzo child in time element."))?;
-        let utc = utc.ok_or(Error::ParseError("Missing utc child in time element."))?;
+        let tzo = tzo.ok_or(Error::Other("Missing tzo child in time element."))?;
+        let utc = utc.ok_or(Error::Other("Missing utc child in time element."))?;
         let date = utc.with_timezone(tzo);
 
         Ok(TimeResult(date))

parsers/src/tune.rs ๐Ÿ”—

@@ -6,8 +6,8 @@
 
 use crate::ns;
 use crate::pubsub::PubSubPayload;
-use crate::util::error::Error;
 use crate::Element;
+use xso::error::{Error, FromElementError};
 
 generate_elem_id!(
     /// The artist or performer of the song or piece.
@@ -106,9 +106,9 @@ impl Tune {
 }
 
 impl TryFrom<Element> for Tune {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<Tune, Error> {
+    fn try_from(elem: Element) -> Result<Tune, FromElementError> {
         check_self!(elem, "tune", TUNE);
         check_no_attributes!(elem, "tune");
 
@@ -116,41 +116,41 @@ impl TryFrom<Element> for Tune {
         for child in elem.children() {
             if child.is("artist", ns::TUNE) {
                 if tune.artist.is_some() {
-                    return Err(Error::ParseError("Tune canโ€™t have more than one artist."));
+                    return Err(Error::Other("Tune canโ€™t have more than one artist.").into());
                 }
                 tune.artist = Some(Artist::try_from(child.clone())?);
             } else if child.is("length", ns::TUNE) {
                 if tune.length.is_some() {
-                    return Err(Error::ParseError("Tune canโ€™t have more than one length."));
+                    return Err(Error::Other("Tune canโ€™t have more than one length.").into());
                 }
                 tune.length = Some(Length::try_from(child.clone())?);
             } else if child.is("rating", ns::TUNE) {
                 if tune.rating.is_some() {
-                    return Err(Error::ParseError("Tune canโ€™t have more than one rating."));
+                    return Err(Error::Other("Tune canโ€™t have more than one rating.").into());
                 }
                 tune.rating = Some(Rating::try_from(child.clone())?);
             } else if child.is("source", ns::TUNE) {
                 if tune.source.is_some() {
-                    return Err(Error::ParseError("Tune canโ€™t have more than one source."));
+                    return Err(Error::Other("Tune canโ€™t have more than one source.").into());
                 }
                 tune.source = Some(Source::try_from(child.clone())?);
             } else if child.is("title", ns::TUNE) {
                 if tune.title.is_some() {
-                    return Err(Error::ParseError("Tune canโ€™t have more than one title."));
+                    return Err(Error::Other("Tune canโ€™t have more than one title.").into());
                 }
                 tune.title = Some(Title::try_from(child.clone())?);
             } else if child.is("track", ns::TUNE) {
                 if tune.track.is_some() {
-                    return Err(Error::ParseError("Tune canโ€™t have more than one track."));
+                    return Err(Error::Other("Tune canโ€™t have more than one track.").into());
                 }
                 tune.track = Some(Track::try_from(child.clone())?);
             } else if child.is("uri", ns::TUNE) {
                 if tune.uri.is_some() {
-                    return Err(Error::ParseError("Tune canโ€™t have more than one uri."));
+                    return Err(Error::Other("Tune canโ€™t have more than one uri.").into());
                 }
                 tune.uri = Some(Uri::try_from(child.clone())?);
             } else {
-                return Err(Error::ParseError("Unknown element in User Tune."));
+                return Err(Error::Other("Unknown element in User Tune.").into());
             }
         }
 

parsers/src/util/error.rs ๐Ÿ”—

@@ -1,148 +0,0 @@
-// Copyright (c) 2017-2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-use std::error::Error as StdError;
-use std::fmt;
-
-/// Contains one of the potential errors triggered while parsing an
-/// [Element](../struct.Element.html) into a specialised struct.
-#[derive(Debug)]
-pub enum Error {
-    /// The usual error when parsing something.
-    ///
-    /// TODO: use a structured error so the user can report it better, instead
-    /// of a freeform string.
-    ParseError(&'static str),
-
-    /// Element local-name/namespace mismatch
-    ///
-    /// Returns the original element unaltered, as well as the expected ns and
-    /// local-name.
-    TypeMismatch(&'static str, &'static str, crate::Element),
-
-    /// Generated when some base64 content fails to decode, usually due to
-    /// extra characters.
-    Base64Error(base64::DecodeError),
-
-    /// Generated when text which should be an integer fails to parse.
-    ParseIntError(std::num::ParseIntError),
-
-    /// Generated when text which should be a string fails to parse.
-    ParseStringError(std::string::ParseError),
-
-    /// Generated when text which should be an IP address (IPv4 or IPv6) fails
-    /// to parse.
-    ParseAddrError(std::net::AddrParseError),
-
-    /// Generated when text which should be a [JID](../../jid/struct.Jid.html)
-    /// fails to parse.
-    JidParseError(jid::Error),
-
-    /// Generated when text which should be a
-    /// [DateTime](../date/struct.DateTime.html) fails to parse.
-    ChronoParseError(chrono::ParseError),
-}
-
-impl Error {
-    /// Converts the TypeMismatch error to a generic ParseError
-    ///
-    /// This must be used when TryFrom is called on children to avoid confusing
-    /// user code which assumes that TypeMismatch refers to the top level
-    /// element only.
-    pub(crate) fn hide_type_mismatch(self) -> Self {
-        match self {
-            Error::TypeMismatch(..) => Error::ParseError("Unexpected child element"),
-            other => other,
-        }
-    }
-}
-
-impl StdError for Error {
-    fn cause(&self) -> Option<&dyn StdError> {
-        match self {
-            Error::ParseError(_) | Error::TypeMismatch(..) => None,
-            Error::Base64Error(e) => Some(e),
-            Error::ParseIntError(e) => Some(e),
-            Error::ParseStringError(e) => Some(e),
-            Error::ParseAddrError(e) => Some(e),
-            Error::JidParseError(e) => Some(e),
-            Error::ChronoParseError(e) => Some(e),
-        }
-    }
-}
-
-impl fmt::Display for Error {
-    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Error::ParseError(s) => write!(fmt, "parse error: {}", s),
-            Error::TypeMismatch(ns, localname, element) => write!(
-                fmt,
-                "element type mismatch: expected {{{}}}{}, got {{{}}}{}",
-                ns,
-                localname,
-                element.ns(),
-                element.name()
-            ),
-            Error::Base64Error(e) => write!(fmt, "base64 error: {}", e),
-            Error::ParseIntError(e) => write!(fmt, "integer parsing error: {}", e),
-            Error::ParseStringError(e) => write!(fmt, "string parsing error: {}", e),
-            Error::ParseAddrError(e) => write!(fmt, "IP address parsing error: {}", e),
-            Error::JidParseError(e) => write!(fmt, "JID parsing error: {}", e),
-            Error::ChronoParseError(e) => write!(fmt, "time parsing error: {}", e),
-        }
-    }
-}
-
-impl From<base64::DecodeError> for Error {
-    fn from(err: base64::DecodeError) -> Error {
-        Error::Base64Error(err)
-    }
-}
-
-impl From<std::num::ParseIntError> for Error {
-    fn from(err: std::num::ParseIntError) -> Error {
-        Error::ParseIntError(err)
-    }
-}
-
-impl From<std::string::ParseError> for Error {
-    fn from(err: std::string::ParseError) -> Error {
-        Error::ParseStringError(err)
-    }
-}
-
-impl From<std::net::AddrParseError> for Error {
-    fn from(err: std::net::AddrParseError) -> Error {
-        Error::ParseAddrError(err)
-    }
-}
-
-impl From<jid::Error> for Error {
-    fn from(err: jid::Error) -> Error {
-        Error::JidParseError(err)
-    }
-}
-
-impl From<chrono::ParseError> for Error {
-    fn from(err: chrono::ParseError) -> Error {
-        Error::ChronoParseError(err)
-    }
-}
-
-impl From<Error> for xso::error::Error {
-    fn from(other: Error) -> Self {
-        match other {
-            Error::ParseError(e) => Self::Other(e.to_string().into()),
-            Error::TypeMismatch { .. } => Self::TypeMismatch,
-            Error::Base64Error(e) => Self::TextParseError(Box::new(e)),
-            Error::ParseIntError(e) => Self::TextParseError(Box::new(e)),
-            Error::ParseStringError(e) => Self::TextParseError(Box::new(e)),
-            Error::ParseAddrError(e) => Self::TextParseError(Box::new(e)),
-            Error::JidParseError(e) => Self::TextParseError(Box::new(e)),
-            Error::ChronoParseError(e) => Self::TextParseError(Box::new(e)),
-        }
-    }
-}

parsers/src/util/macros.rs ๐Ÿ”—

@@ -6,7 +6,13 @@
 
 macro_rules! get_attr {
     ($elem:ident, $attr:tt, $type:tt) => {
-        get_attr!($elem, $attr, $type, value, value.parse()?)
+        get_attr!(
+            $elem,
+            $attr,
+            $type,
+            value,
+            value.parse().map_err(xso::error::Error::text_parse_error)?
+        )
     };
     ($elem:ident, $attr:tt, OptionEmpty, $value:ident, $func:expr) => {
         match $elem.attr($attr) {
@@ -25,30 +31,27 @@ macro_rules! get_attr {
         match $elem.attr($attr) {
             Some($value) => $func,
             None => {
-                return Err(crate::util::error::Error::ParseError(concat!(
-                    "Required attribute '",
-                    $attr,
-                    "' missing."
-                )));
+                return Err(xso::error::Error::Other(
+                    concat!("Required attribute '", $attr, "' missing.").into(),
+                )
+                .into());
             }
         }
     };
     ($elem:ident, $attr:tt, RequiredNonEmpty, $value:ident, $func:expr) => {
         match $elem.attr($attr) {
             Some("") => {
-                return Err(crate::util::error::Error::ParseError(concat!(
-                    "Required attribute '",
-                    $attr,
-                    "' must not be empty."
-                )));
+                return Err(xso::error::Error::Other(
+                    concat!("Required attribute '", $attr, "' must not be empty.").into(),
+                )
+                .into());
             }
             Some($value) => $func,
             None => {
-                return Err(crate::util::error::Error::ParseError(concat!(
-                    "Required attribute '",
-                    $attr,
-                    "' missing."
-                )));
+                return Err(xso::error::Error::Other(
+                    concat!("Required attribute '", $attr, "' missing.").into(),
+                )
+                .into());
             }
         }
     };
@@ -71,11 +74,11 @@ macro_rules! generate_attribute {
             ),+
         }
         impl ::std::str::FromStr for $elem {
-            type Err = crate::util::error::Error;
-            fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
+            type Err = xso::error::Error;
+            fn from_str(s: &str) -> Result<$elem, xso::error::Error> {
                 Ok(match s {
                     $($b => $elem::$a),+,
-                    _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
+                    _ => return Err(xso::error::Error::Other(concat!("Unknown value for '", $name, "' attribute.")).into()),
                 })
             }
         }
@@ -104,11 +107,11 @@ macro_rules! generate_attribute {
             ),+
         }
         impl ::std::str::FromStr for $elem {
-            type Err = crate::util::error::Error;
-            fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
+            type Err = xso::error::Error;
+            fn from_str(s: &str) -> Result<$elem, xso::error::Error> {
                 Ok(match s {
                     $($b => $elem::$a),+,
-                    _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
+                    _ => return Err(xso::error::Error::Other(concat!("Unknown value for '", $name, "' attribute.")).into()),
                 })
             }
         }
@@ -137,11 +140,11 @@ macro_rules! generate_attribute {
             None,
         }
         impl ::std::str::FromStr for $elem {
-            type Err = crate::util::error::Error;
-            fn from_str(s: &str) -> Result<Self, crate::util::error::Error> {
+            type Err = xso::error::Error;
+            fn from_str(s: &str) -> Result<Self, xso::error::Error> {
                 Ok(match s {
                     $value => $elem::$symbol,
-                    _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
+                    _ => return Err(xso::error::Error::Other(concat!("Unknown value for '", $name, "' attribute."))),
                 })
             }
         }
@@ -169,12 +172,12 @@ macro_rules! generate_attribute {
             False,
         }
         impl ::std::str::FromStr for $elem {
-            type Err = crate::util::error::Error;
-            fn from_str(s: &str) -> Result<Self, crate::util::error::Error> {
+            type Err = xso::error::Error;
+            fn from_str(s: &str) -> Result<Self, xso::error::Error> {
                 Ok(match s {
                     "true" | "1" => $elem::True,
                     "false" | "0" => $elem::False,
-                    _ => return Err(crate::util::error::Error::ParseError(concat!("Unknown value for '", $name, "' attribute."))),
+                    _ => return Err(xso::error::Error::Other(concat!("Unknown value for '", $name, "' attribute."))),
                 })
             }
         }
@@ -197,9 +200,9 @@ macro_rules! generate_attribute {
         #[derive(Debug, Clone, PartialEq)]
         pub struct $elem(pub $type);
         impl ::std::str::FromStr for $elem {
-            type Err = crate::util::error::Error;
-            fn from_str(s: &str) -> Result<Self, crate::util::error::Error> {
-                Ok($elem($type::from_str(s)?))
+            type Err = xso::error::Error;
+            fn from_str(s: &str) -> Result<Self, xso::error::Error> {
+                Ok($elem($type::from_str(s).map_err(xso::error::Error::text_parse_error)?))
             }
         }
         impl ::minidom::IntoAttributeValue for $elem {
@@ -229,14 +232,14 @@ macro_rules! generate_element_enum {
             ),+
         }
         impl ::std::convert::TryFrom<crate::Element> for $elem {
-            type Error = crate::util::error::Error;
-            fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> {
+            type Error = xso::error::FromElementError;
+            fn try_from(elem: crate::Element) -> Result<$elem, xso::error::FromElementError> {
                 check_ns_only!(elem, $name, $ns);
                 check_no_children!(elem, $name);
                 check_no_attributes!(elem, $name);
                 Ok(match elem.name() {
                     $($enum_name => $elem::$enum,)+
-                    _ => return Err(crate::util::error::Error::ParseError(concat!("This is not a ", $name, " element."))),
+                    _ => return Err(xso::error::Error::Other(concat!("This is not a ", $name, " element.")).into()),
                 })
             }
         }
@@ -265,14 +268,14 @@ macro_rules! generate_attribute_enum {
             ),+
         }
         impl ::std::convert::TryFrom<crate::Element> for $elem {
-            type Error = crate::util::error::Error;
-            fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> {
+            type Error = xso::error::FromElementError;
+            fn try_from(elem: crate::Element) -> Result<$elem, xso::error::FromElementError> {
                 check_ns_only!(elem, $name, $ns);
                 check_no_children!(elem, $name);
                 check_no_unknown_attributes!(elem, $name, [$attr]);
                 Ok(match get_attr!(elem, $attr, Required) {
                     $($enum_name => $elem::$enum,)+
-                    _ => return Err(crate::util::error::Error::ParseError(concat!("Invalid ", $name, " ", $attr, " value."))),
+                    _ => return Err(xso::error::Error::Other(concat!("Invalid ", $name, " ", $attr, " value.")).into()),
                 })
             }
         }
@@ -294,11 +297,7 @@ macro_rules! check_self {
     };
     ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
         if !$elem.is($name, crate::ns::$ns) {
-            return Err(crate::util::error::Error::TypeMismatch(
-                $name,
-                crate::ns::$ns,
-                $elem,
-            ));
+            return Err(xso::error::FromElementError::Mismatch($elem));
         }
     };
 }
@@ -309,11 +308,10 @@ macro_rules! check_child {
     };
     ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => {
         if !$elem.is($name, crate::ns::$ns) {
-            return Err(crate::util::error::Error::ParseError(concat!(
-                "This is not a ",
-                $pretty_name,
-                " element."
-            )));
+            return Err(xso::error::Error::Other(
+                concat!("This is not a ", $pretty_name, " element.").into(),
+            )
+            .into());
         }
     };
 }
@@ -321,11 +319,10 @@ macro_rules! check_child {
 macro_rules! check_ns_only {
     ($elem:ident, $name:tt, $ns:ident) => {
         if !$elem.has_ns(crate::ns::$ns) {
-            return Err(crate::util::error::Error::ParseError(concat!(
-                "This is not a ",
-                $name,
-                " element."
-            )));
+            return Err(xso::error::Error::Other(
+                concat!("This is not a ", $name, " element.").into(),
+            )
+            .into());
         }
     };
 }
@@ -334,11 +331,10 @@ macro_rules! check_no_children {
     ($elem:ident, $name:tt) => {
         #[cfg(not(feature = "disable-validation"))]
         for _ in $elem.children() {
-            return Err(crate::util::error::Error::ParseError(concat!(
-                "Unknown child in ",
-                $name,
-                " element."
-            )));
+            return Err(xso::error::Error::Other(
+                concat!("Unknown child in ", $name, " element.").into(),
+            )
+            .into());
         }
     };
 }
@@ -347,11 +343,10 @@ macro_rules! check_no_attributes {
     ($elem:ident, $name:tt) => {
         #[cfg(not(feature = "disable-validation"))]
         for _ in $elem.attrs() {
-            return Err(crate::util::error::Error::ParseError(concat!(
-                "Unknown attribute in ",
-                $name,
-                " element."
-            )));
+            return Err(xso::error::Error::Other(
+                concat!("Unknown attribute in ", $name, " element.").into(),
+            )
+            .into());
         }
     };
 }
@@ -365,7 +360,7 @@ macro_rules! check_no_unknown_attributes {
                     continue;
                 }
             )*
-            return Err(crate::util::error::Error::ParseError(concat!("Unknown attribute in ", $name, " element.")));
+            return Err(xso::error::Error::Other(concat!("Unknown attribute in ", $name, " element.")).into());
         }
     );
 }
@@ -377,9 +372,9 @@ macro_rules! generate_empty_element {
         pub struct $elem;
 
         impl ::std::convert::TryFrom<crate::Element> for $elem {
-            type Error = crate::util::error::Error;
+            type Error = xso::error::FromElementError;
 
-            fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> {
+            fn try_from(elem: crate::Element) -> Result<$elem, xso::error::FromElementError> {
                 check_self!(elem, $name, $ns);
                 check_no_children!(elem, $name);
                 check_no_attributes!(elem, $name);
@@ -402,8 +397,8 @@ macro_rules! generate_id {
         #[derive(Debug, Clone, PartialEq, Eq, Hash)]
         pub struct $elem(pub String);
         impl ::std::str::FromStr for $elem {
-            type Err = crate::util::error::Error;
-            fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
+            type Err = xso::error::Error;
+            fn from_str(s: &str) -> Result<$elem, xso::error::Error> {
                 // TODO: add a way to parse that differently when needed.
                 Ok($elem(String::from(s)))
             }
@@ -420,8 +415,8 @@ macro_rules! generate_elem_id {
     ($(#[$meta:meta])* $elem:ident, $name:tt, $ns:ident) => (
         generate_elem_id!($(#[$meta])* $elem, $name, $ns, String);
         impl ::std::str::FromStr for $elem {
-            type Err = crate::util::error::Error;
-            fn from_str(s: &str) -> Result<$elem, crate::util::error::Error> {
+            type Err = xso::error::Error;
+            fn from_str(s: &str) -> Result<$elem, xso::error::Error> {
                 // TODO: add a way to parse that differently when needed.
                 Ok($elem(String::from(s)))
             }
@@ -432,13 +427,13 @@ macro_rules! generate_elem_id {
         #[derive(Debug, Clone, PartialEq, Eq, Hash)]
         pub struct $elem(pub $type);
         impl ::std::convert::TryFrom<crate::Element> for $elem {
-            type Error = crate::util::error::Error;
-            fn try_from(elem: crate::Element) -> Result<$elem, crate::util::error::Error> {
+            type Error = xso::error::FromElementError;
+            fn try_from(elem: crate::Element) -> Result<$elem, xso::error::FromElementError> {
                 check_self!(elem, $name, $ns);
                 check_no_children!(elem, $name);
                 check_no_attributes!(elem, $name);
                 // TODO: add a way to parse that differently when needed.
-                Ok($elem(elem.text().parse()?))
+                Ok($elem(elem.text().parse().map_err(xso::error::Error::text_parse_error)?))
             }
         }
         impl From<$elem> for crate::Element {
@@ -507,7 +502,7 @@ macro_rules! do_parse {
         Ok($elem.text())
     };
     ($elem:ident, $constructor:ident) => {
-        $constructor::try_from($elem)
+        $constructor::try_from($elem).map_err(xso::error::Error::from)
     };
 }
 
@@ -520,13 +515,16 @@ macro_rules! do_parse_elem {
     };
     ($temp:ident: Option = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
         if $temp.is_some() {
-            Err(crate::util::error::Error::ParseError(concat!(
-                "Element ",
-                $parent_name,
-                " must not have more than one ",
-                $name,
-                " child."
-            )))
+            Err(xso::error::Error::Other(
+                concat!(
+                    "Element ",
+                    $parent_name,
+                    " must not have more than one ",
+                    $name,
+                    " child."
+                )
+                .into(),
+            ))
         } else {
             match do_parse!($elem, $constructor) {
                 Ok(v) => {
@@ -539,13 +537,16 @@ macro_rules! do_parse_elem {
     };
     ($temp:ident: Required = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
         if $temp.is_some() {
-            Err(crate::util::error::Error::ParseError(concat!(
-                "Element ",
-                $parent_name,
-                " must not have more than one ",
-                $name,
-                " child."
-            )))
+            Err(xso::error::Error::Other(
+                concat!(
+                    "Element ",
+                    $parent_name,
+                    " must not have more than one ",
+                    $name,
+                    " child."
+                )
+                .into(),
+            ))
         } else {
             match do_parse!($elem, $constructor) {
                 Ok(v) => {
@@ -558,13 +559,16 @@ macro_rules! do_parse_elem {
     };
     ($temp:ident: Present = $constructor:ident => $elem:ident, $name:tt, $parent_name:tt) => {
         if $temp {
-            Err(crate::util::error::Error::ParseError(concat!(
-                "Element ",
-                $parent_name,
-                " must not have more than one ",
-                $name,
-                " child."
-            )))
+            Err(xso::error::Error::Other(
+                concat!(
+                    "Element ",
+                    $parent_name,
+                    " must not have more than one ",
+                    $name,
+                    " child."
+                )
+                .into(),
+            ))
         } else {
             $temp = true;
             Ok(())
@@ -580,13 +584,9 @@ macro_rules! finish_parse_elem {
         $temp
     };
     ($temp:ident: Required = $name:tt, $parent_name:tt) => {
-        $temp.ok_or(crate::util::error::Error::ParseError(concat!(
-            "Missing child ",
-            $name,
-            " in ",
-            $parent_name,
-            " element."
-        )))?
+        $temp.ok_or(xso::error::Error::Other(
+            concat!("Missing child ", $name, " in ", $parent_name, " element.").into(),
+        ))?
     };
     ($temp:ident: Present = $name:tt, $parent_name:tt) => {
         $temp
@@ -694,9 +694,9 @@ macro_rules! generate_element {
         }
 
         impl ::std::convert::TryFrom<crate::Element> for $elem {
-            type Error = crate::util::error::Error;
+            type Error = xso::error::FromElementError;
 
-            fn try_from(mut elem: crate::Element) -> Result<$elem, crate::util::error::Error> {
+            fn try_from(mut elem: crate::Element) -> Result<$elem, xso::error::FromElementError> {
                 check_self!(elem, $name, $ns);
                 check_no_unknown_attributes!(elem, $name, [$($attr_name),*]);
                 $(
@@ -726,14 +726,14 @@ macro_rules! generate_element {
                     let residual = if generate_child_test!(residual, $child_name, $child_ns) {
                         match do_parse_elem!($child_ident: $coucou = $child_constructor => residual, $child_name, $name) {
                             Ok(()) => continue,
-                            Err(other) => return Err(other),
+                            Err(other) => return Err(other.into()),
                         }
                     } else {
                         residual
                     };
                     )*
                     let _ = residual;
-                    return Err(crate::util::error::Error::ParseError(concat!("Unknown child in ", $name, " element.")));
+                    return Err(xso::error::Error::Other(concat!("Unknown child in ", $name, " element.")).into());
                 }
                 Ok($elem {
                     $(
@@ -787,17 +787,15 @@ macro_rules! assert_size (
 macro_rules! impl_pubsub_item {
     ($item:ident, $ns:ident) => {
         impl ::std::convert::TryFrom<crate::Element> for $item {
-            type Error = Error;
+            type Error = FromElementError;
 
-            fn try_from(mut elem: crate::Element) -> Result<$item, Error> {
+            fn try_from(mut elem: crate::Element) -> Result<$item, FromElementError> {
                 check_self!(elem, "item", $ns);
                 check_no_unknown_attributes!(elem, "item", ["id", "publisher"]);
                 let mut payloads = elem.take_contents_as_children().collect::<Vec<_>>();
                 let payload = payloads.pop();
                 if !payloads.is_empty() {
-                    return Err(Error::ParseError(
-                        "More than a single payload in item element.",
-                    ));
+                    return Err(Error::Other("More than a single payload in item element.").into());
                 }
                 Ok($item(crate::pubsub::Item {
                     id: get_attr!(elem, "id", Option),

parsers/src/util/mod.rs ๐Ÿ”—

@@ -4,9 +4,6 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-/// Error type returned by every parser on failure.
-pub mod error;
-
 /// Various helpers.
 pub(crate) mod text_node_codecs;
 

parsers/src/util/text_node_codecs.rs ๐Ÿ”—

@@ -4,10 +4,10 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-use crate::util::error::Error;
 use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
 use jid::Jid;
 use std::str::FromStr;
+use xso::error::Error;
 
 /// A trait for codecs that can decode and encode text nodes.
 pub trait Codec {
@@ -70,7 +70,7 @@ where
         match s.trim() {
             // TODO: This error message can be a bit opaque when used
             // in-context; ideally it'd be configurable.
-            "" => Err(Error::ParseError(
+            "" => Err(Error::Other(
                 "The text in the element's text node was empty after trimming.",
             )),
             trimmed => T::decode(trimmed),
@@ -89,7 +89,7 @@ impl Codec for Base64 {
     type Decoded = Vec<u8>;
 
     fn decode(s: &str) -> Result<Vec<u8>, Error> {
-        Ok(Base64Engine.decode(s)?)
+        Base64Engine.decode(s).map_err(Error::text_parse_error)
     }
 
     fn encode(decoded: &Vec<u8>) -> Option<String> {
@@ -109,7 +109,7 @@ impl Codec for WhitespaceAwareBase64 {
             .filter(|ch| *ch != ' ' && *ch != '\n' && *ch != '\t')
             .collect();
 
-        Ok(Base64Engine.decode(s)?)
+        Base64Engine.decode(s).map_err(Error::text_parse_error)
     }
 
     fn encode(decoded: &Self::Decoded) -> Option<String> {
@@ -125,12 +125,13 @@ impl<const N: usize> Codec for FixedHex<N> {
 
     fn decode(s: &str) -> Result<Self::Decoded, Error> {
         if s.len() != 2 * N {
-            return Err(Error::ParseError("Invalid length"));
+            return Err(Error::Other("Invalid length"));
         }
 
         let mut bytes = [0u8; N];
         for i in 0..N {
-            bytes[i] = u8::from_str_radix(&s[2 * i..2 * i + 2], 16)?;
+            bytes[i] =
+                u8::from_str_radix(&s[2 * i..2 * i + 2], 16).map_err(Error::text_parse_error)?;
         }
 
         Ok(bytes)
@@ -154,7 +155,8 @@ impl Codec for ColonSeparatedHex {
     fn decode(s: &str) -> Result<Self::Decoded, Error> {
         let mut bytes = vec![];
         for i in 0..(1 + s.len()) / 3 {
-            let byte = u8::from_str_radix(&s[3 * i..3 * i + 2], 16)?;
+            let byte =
+                u8::from_str_radix(&s[3 * i..3 * i + 2], 16).map_err(Error::text_parse_error)?;
             if 3 * i + 2 < s.len() {
                 assert_eq!(&s[3 * i + 2..3 * i + 3], ":");
             }
@@ -179,7 +181,7 @@ impl Codec for JidCodec {
     type Decoded = Jid;
 
     fn decode(s: &str) -> Result<Jid, Error> {
-        Ok(Jid::from_str(s)?)
+        Jid::from_str(s).map_err(Error::text_parse_error)
     }
 
     fn encode(jid: &Jid) -> Option<String> {
@@ -205,28 +207,28 @@ mod tests {
 
         // What if we give it a string that's too long?
         let err = FixedHex::<3>::decode("01feEF01").unwrap_err();
-        assert_eq!(err.to_string(), "parse error: Invalid length");
+        assert_eq!(err.to_string(), "Invalid length");
 
         // Too short?
         let err = FixedHex::<3>::decode("01fe").unwrap_err();
-        assert_eq!(err.to_string(), "parse error: Invalid length");
+        assert_eq!(err.to_string(), "Invalid length");
 
         // Not-even numbers?
         let err = FixedHex::<3>::decode("01feE").unwrap_err();
-        assert_eq!(err.to_string(), "parse error: Invalid length");
+        assert_eq!(err.to_string(), "Invalid length");
 
         // No colon supported.
         let err = FixedHex::<3>::decode("0:f:EF").unwrap_err();
         assert_eq!(
             err.to_string(),
-            "integer parsing error: invalid digit found in string"
+            "text parse error: invalid digit found in string"
         );
 
         // No non-hex character allowed.
         let err = FixedHex::<3>::decode("01defg").unwrap_err();
         assert_eq!(
             err.to_string(),
-            "integer parsing error: invalid digit found in string"
+            "text parse error: invalid digit found in string"
         );
     }
 }

parsers/src/vcard.rs ๐Ÿ”—

@@ -55,14 +55,12 @@ pub struct VCard {
 }
 
 impl TryFrom<Element> for VCard {
-    type Error = crate::util::error::Error;
+    type Error = xso::error::Error;
 
     fn try_from(value: Element) -> Result<Self, Self::Error> {
         // Check that the root element is <vCard>
         if !value.is("vCard", ns::VCARD) {
-            return Err(Error::ParseError(
-                "Root element is not <vCard xmlns='vcard-temp'>",
-            ));
+            return Err(Error::Other("Root element is not <vCard xmlns='vcard-temp'>").into());
         }
 
         // Parse the <PHOTO> element, if any.

parsers/src/xhtml.rs ๐Ÿ”—

@@ -6,9 +6,9 @@
 
 use crate::message::MessagePayload;
 use crate::ns;
-use crate::util::error::Error;
 use minidom::{Element, Node};
 use std::collections::HashMap;
+use xso::error::{Error, FromElementError};
 
 // TODO: Use a proper lang type.
 type Lang = String;
@@ -60,9 +60,9 @@ impl XhtmlIm {
 impl MessagePayload for XhtmlIm {}
 
 impl TryFrom<Element> for XhtmlIm {
-    type Error = Error;
+    type Error = FromElementError;
 
-    fn try_from(elem: Element) -> Result<XhtmlIm, Error> {
+    fn try_from(elem: Element) -> Result<XhtmlIm, FromElementError> {
         check_self!(elem, "html", XHTML_IM);
         check_no_attributes!(elem, "html");
 
@@ -75,13 +75,14 @@ impl TryFrom<Element> for XhtmlIm {
                 match bodies.insert(lang, body) {
                     None => (),
                     Some(_) => {
-                        return Err(Error::ParseError(
+                        return Err(Error::Other(
                             "Two identical language bodies found in XHTML-IM.",
-                        ))
+                        )
+                        .into())
                     }
                 }
             } else {
-                return Err(Error::ParseError("Unknown element in XHTML-IM."));
+                return Err(Error::Other("Unknown element in XHTML-IM.").into());
             }
         }
 
@@ -544,7 +545,7 @@ mod tests {
             .unwrap();
         let error = XhtmlIm::try_from(elem).unwrap_err();
         let message = match error {
-            Error::ParseError(string) => string,
+            FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
         assert_eq!(message, "Two identical language bodies found in XHTML-IM.");

xso/src/error.rs ๐Ÿ”—

@@ -23,7 +23,7 @@ pub enum Error {
     TextParseError(Box<dyn std::error::Error + Send + Sync + 'static>),
 
     /// Generic, unspecified other error.
-    Other(Box<str>),
+    Other(&'static str),
 
     /// An element header did not match an expected element.
     ///
@@ -33,6 +33,16 @@ pub enum Error {
     TypeMismatch,
 }
 
+impl Error {
+    /// Convenience function to create a [`Self::TextParseError`] variant.
+    ///
+    /// This includes the `Box::new(.)` call, making it directly usable as
+    /// argument to [`Result::map_err`].
+    pub fn text_parse_error<T: std::error::Error + Send + Sync + 'static>(e: T) -> Self {
+        Self::TextParseError(Box::new(e))
+    }
+}
+
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
@@ -121,3 +131,75 @@ impl std::error::Error for FromEventsError {
         }
     }
 }
+
+impl From<Error> for Result<minidom::Element, Error> {
+    fn from(other: Error) -> Self {
+        Self::Err(other)
+    }
+}
+
+/// Error returned by the `TryFrom<Element>` implementations.
+#[derive(Debug)]
+pub enum FromElementError {
+    /// The XML element header did not match the expectations of the type
+    /// implementing `TryFrom`.
+    ///
+    /// Contains the original `Element` unmodified.
+    Mismatch(minidom::Element),
+
+    /// During processing of the element, an (unrecoverable) error occured.
+    Invalid(Error),
+}
+
+impl fmt::Display for FromElementError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::Mismatch(ref el) => write!(
+                f,
+                "expected different XML element (got {} in namespace {})",
+                el.name(),
+                el.ns()
+            ),
+            Self::Invalid(ref e) => fmt::Display::fmt(e, f),
+        }
+    }
+}
+
+impl std::error::Error for FromElementError {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        match self {
+            Self::Mismatch(_) => None,
+            Self::Invalid(ref e) => Some(e),
+        }
+    }
+}
+
+impl From<Result<minidom::Element, Error>> for FromElementError {
+    fn from(other: Result<minidom::Element, Error>) -> Self {
+        match other {
+            Ok(v) => Self::Mismatch(v),
+            Err(e) => Self::Invalid(e),
+        }
+    }
+}
+
+impl From<Error> for FromElementError {
+    fn from(other: Error) -> Self {
+        Self::Invalid(other)
+    }
+}
+
+impl From<FromElementError> for Error {
+    fn from(other: FromElementError) -> Self {
+        match other {
+            FromElementError::Invalid(e) => e,
+            FromElementError::Mismatch(..) => Self::TypeMismatch,
+        }
+    }
+}
+
+impl From<core::convert::Infallible> for FromElementError {
+    fn from(other: core::convert::Infallible) -> Self {
+        match other {}
+    }
+}

xso/src/lib.rs ๐Ÿ”—

@@ -138,3 +138,38 @@ pub fn transform<T: FromXml, F: IntoXml>(from: F) -> Result<T, self::error::Erro
         rxml::error::XmlError::InvalidEof("during transform"),
     ))
 }
+
+/// Attempt to convert a [`minidom::Element`] into a type implementing
+/// [`FromXml`], fallably.
+///
+/// Unlike [`transform`] (which can also be used with an element), this
+/// function will return the element unharmed if its element header does not
+/// match the expectations of `T`.
+pub fn try_from_element<T: FromXml>(
+    from: minidom::Element,
+) -> Result<T, self::error::FromElementError> {
+    let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
+    let mut sink = match T::from_events(qname, attrs) {
+        Ok(v) => v,
+        Err(self::error::FromEventsError::Mismatch { .. }) => {
+            return Err(self::error::FromElementError::Mismatch(from))
+        }
+        Err(self::error::FromEventsError::Invalid(e)) => {
+            return Err(self::error::FromElementError::Invalid(e))
+        }
+    };
+
+    let mut iter = from.into_event_iter()?;
+    iter.next().expect("first event from minidom::Element")?;
+    for event in iter {
+        let event = event?;
+        match sink.feed(event)? {
+            Some(v) => return Ok(v),
+            None => (),
+        }
+    }
+    // unreachable! instead of error here, because minidom::Element always
+    // produces the complete event sequence of a single element, and FromXml
+    // implementations must be constructible from that.
+    unreachable!("minidom::Element did not produce enough events to complete element")
+}

xso/src/minidom_compat.rs ๐Ÿ”—

@@ -55,7 +55,7 @@ enum IntoEventsInner {
 // NOTE to developers: The limitations are not fully trivial to overcome:
 // the attributes use a BTreeMap internally, which does not offer a `drain`
 // iterator.
-fn make_start_ev_parts(el: &Element) -> Result<(rxml::QName, AttrMap), Error> {
+pub fn make_start_ev_parts(el: &Element) -> Result<(rxml::QName, AttrMap), Error> {
     let name = NcName::try_from(el.name())?;
     let namespace = Namespace::from(el.ns());