diff --git a/parsers/src/blocking.rs b/parsers/src/blocking.rs index 464b139..8acb588 100644 --- a/parsers/src/blocking.rs +++ b/parsers/src/blocking.rs @@ -37,7 +37,7 @@ macro_rules! generate_blocking_element { check_no_attributes!(elem, $name); let mut items = vec!(); for child in elem.children() { - check_self!(child, "item", BLOCKING); + check_child!(child, "item", BLOCKING); check_no_unknown_attributes!(child, "item", ["jid"]); check_no_children!(child, "item"); items.push(get_attr!(child, "jid", Required)); diff --git a/parsers/src/delay.rs b/parsers/src/delay.rs index 64d7f49..a38f4ec 100644 --- a/parsers/src/delay.rs +++ b/parsers/src/delay.rs @@ -69,12 +69,12 @@ mod tests { let elem: Element = "" .parse() .unwrap(); - let error = Delay::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, + let error = Delay::try_from(elem.clone()).unwrap_err(); + let returned_elem = match error { + Error::TypeMismatch(_, _, elem) => elem, _ => panic!(), }; - assert_eq!(message, "This is not a delay element."); + assert_eq!(elem, returned_elem); } #[test] diff --git a/parsers/src/eme.rs b/parsers/src/eme.rs index 5a624a3..256b878 100644 --- a/parsers/src/eme.rs +++ b/parsers/src/eme.rs @@ -59,12 +59,12 @@ mod tests { let elem: Element = "" .parse() .unwrap(); - let error = ExplicitMessageEncryption::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, + let error = ExplicitMessageEncryption::try_from(elem.clone()).unwrap_err(); + let returned_elem = match error { + Error::TypeMismatch(_, _, elem) => elem, _ => panic!(), }; - assert_eq!(message, "This is not a encryption element."); + assert_eq!(elem, returned_elem); } #[test] diff --git a/parsers/src/hashes.rs b/parsers/src/hashes.rs index c79a71d..379f34c 100644 --- a/parsers/src/hashes.rs +++ b/parsers/src/hashes.rs @@ -253,12 +253,12 @@ mod tests { let elem: Element = "" .parse() .unwrap(); - let error = Hash::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, + let error = Hash::try_from(elem.clone()).unwrap_err(); + let returned_elem = match error { + Error::TypeMismatch(_, _, elem) => elem, _ => panic!(), }; - assert_eq!(message, "This is not a hash element."); + assert_eq!(elem, returned_elem); } #[test] diff --git a/parsers/src/jingle_ft.rs b/parsers/src/jingle_ft.rs index 67c7a98..e5a6d81 100644 --- a/parsers/src/jingle_ft.rs +++ b/parsers/src/jingle_ft.rs @@ -289,7 +289,7 @@ impl TryFrom for Checksum { "JingleFT checksum element must have exactly one child.", )); } - file = Some(File::try_from(child.clone())?); + file = Some(File::try_from(child.clone()).map_err(|e| e.hide_type_mismatch())?); } if file.is_none() { return Err(Error::ParseError( @@ -541,9 +541,9 @@ mod tests { let error = Checksum::try_from(elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, - _ => panic!(), + other => panic!("unexpected error: {:?}", other), }; - assert_eq!(message, "This is not a file element."); + assert_eq!(message, "Unexpected child element"); let elem: Element = "w0mcJylzCn+AfvuGdqkty2+KP48=".parse().unwrap(); let error = Checksum::try_from(elem).unwrap_err(); diff --git a/parsers/src/rsm.rs b/parsers/src/rsm.rs index af3aa74..bb5adf3 100644 --- a/parsers/src/rsm.rs +++ b/parsers/src/rsm.rs @@ -213,22 +213,22 @@ mod tests { let elem: Element = "" .parse() .unwrap(); - let error = SetQuery::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, + let error = SetQuery::try_from(elem.clone()).unwrap_err(); + let returned_elem = match error { + Error::TypeMismatch(_, _, elem) => elem, _ => panic!(), }; - assert_eq!(message, "This is not a RSM set element."); + assert_eq!(elem, returned_elem); let elem: Element = "" .parse() .unwrap(); - let error = SetResult::try_from(elem).unwrap_err(); - let message = match error { - Error::ParseError(string) => string, + let error = SetResult::try_from(elem.clone()).unwrap_err(); + let returned_elem = match error { + Error::TypeMismatch(_, _, elem) => elem, _ => panic!(), }; - assert_eq!(message, "This is not a RSM set element."); + assert_eq!(elem, returned_elem); } #[test] diff --git a/parsers/src/util/error.rs b/parsers/src/util/error.rs index a2c6ea7..b541266 100644 --- a/parsers/src/util/error.rs +++ b/parsers/src/util/error.rs @@ -17,6 +17,12 @@ pub enum Error { /// 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), @@ -40,10 +46,24 @@ pub enum Error { 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(_) => None, + Error::ParseError(_) | Error::TypeMismatch(..) => None, Error::Base64Error(e) => Some(e), Error::ParseIntError(e) => Some(e), Error::ParseStringError(e) => Some(e), @@ -58,6 +78,14 @@ 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), diff --git a/parsers/src/util/macros.rs b/parsers/src/util/macros.rs index 996d3e2..8815654 100644 --- a/parsers/src/util/macros.rs +++ b/parsers/src/util/macros.rs @@ -292,6 +292,21 @@ macro_rules! check_self { ($elem:ident, $name:tt, $ns:ident) => { check_self!($elem, $name, $ns, $name); }; + ($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, + )); + } + }; +} + +macro_rules! check_child { + ($elem:ident, $name:tt, $ns:ident) => { + check_child!($elem, $name, $ns, $name); + }; ($elem:ident, $name:tt, $ns:ident, $pretty_name:tt) => { if !$elem.is($name, crate::ns::$ns) { return Err(crate::util::error::Error::ParseError(concat!(