From 1b1661fd827fa60503ff13130c7f323e85215b11 Mon Sep 17 00:00:00 2001 From: Astro Date: Sat, 19 Aug 2017 00:04:18 +0100 Subject: [PATCH] Introduce comparing with namespace support. --- src/chatstates.rs | 3 +- src/compare_elements.rs | 118 ++++++++++++++++++++++++++++++++++++++++ src/disco.rs | 3 +- src/ibr.rs | 5 +- src/iq.rs | 7 ++- src/jingle.rs | 3 +- src/jingle_message.rs | 3 +- src/jingle_s5b.rs | 5 +- src/lib.rs | 4 ++ src/message.rs | 7 ++- src/muc/user.rs | 3 +- src/presence.rs | 3 +- src/pubsub/event.rs | 3 +- src/roster.rs | 3 +- src/rsm.rs | 3 +- src/stanza_error.rs | 3 +- 16 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 src/compare_elements.rs diff --git a/src/chatstates.rs b/src/chatstates.rs index 2cb657f6..9a63a28d 100644 --- a/src/chatstates.rs +++ b/src/chatstates.rs @@ -38,7 +38,8 @@ impl TryFrom for ChatState { type Err = Error; fn try_from(elem: Element) -> Result { - if elem.ns() != Some(ns::CHATSTATES) { + let ns = elem.ns(); + if ns.as_ref().map(|ns| ns.as_str()) != Some(ns::CHATSTATES) { return Err(Error::ParseError("This is not a chatstate element.")); } for _ in elem.children() { diff --git a/src/compare_elements.rs b/src/compare_elements.rs new file mode 100644 index 00000000..d133c85c --- /dev/null +++ b/src/compare_elements.rs @@ -0,0 +1,118 @@ +use minidom::{Node, Element}; + +pub trait NamespaceAwareCompare { + /// Namespace-aware comparison for tests + fn compare_to(&self, other: &Self) -> bool; +} + +impl NamespaceAwareCompare for Node { + fn compare_to(&self, other: &Self) -> bool { + match (self, other) { + (&Node::Element(ref elem1), &Node::Element(ref elem2)) => + Element::compare_to(elem1, elem2), + (&Node::Text(ref text1), &Node::Text(ref text2)) => + text1 == text2, + _ => false, + } + } +} + +impl NamespaceAwareCompare for Element { + fn compare_to(&self, other: &Self) -> bool { + if self.name() == other.name() && + self.ns() == other.ns() && + self.attrs().eq(other.attrs()) + { + let child_elems = self.children().count(); + let text_is_whitespace = self.texts() + .all(|text| text.chars().all(char::is_whitespace)); + if child_elems > 0 && text_is_whitespace { + // Ignore all the whitespace text nodes + self.children().zip(other.children()) + .all(|(node1, node2)| node1.compare_to(node2)) + } else { + // Compare with text nodes + self.nodes().zip(other.nodes()) + .all(|(node1, node2)| node1.compare_to(node2)) + } + } else { + false + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use minidom::Element; + + #[test] + fn simple() { + let elem1: Element = "x 3".parse().unwrap(); + let elem2: Element = "x 3".parse().unwrap(); + assert!(elem1.compare_to(&elem2)); + } + + #[test] + fn wrong_attr_name() { + let elem1: Element = "x 3".parse().unwrap(); + let elem2: Element = "x 3".parse().unwrap(); + assert!(!elem1.compare_to(&elem2)); + } + + #[test] + fn wrong_attr_value() { + let elem1: Element = "x 3".parse().unwrap(); + let elem2: Element = "x 3".parse().unwrap(); + assert!(!elem1.compare_to(&elem2)); + } + + #[test] + fn attr_order() { + let elem1: Element = "".parse().unwrap(); + let elem2: Element = "".parse().unwrap(); + assert!(elem1.compare_to(&elem2)); + } + + #[test] + fn wrong_texts() { + let elem1: Element = "foo".parse().unwrap(); + let elem2: Element = "bar".parse().unwrap(); + assert!(!elem1.compare_to(&elem2)); + } + + #[test] + fn children() { + let elem1: Element = "".parse().unwrap(); + let elem2: Element = "".parse().unwrap(); + assert!(elem1.compare_to(&elem2)); + } + + #[test] + fn wrong_children() { + let elem1: Element = "".parse().unwrap(); + let elem2: Element = "".parse().unwrap(); + assert!(!elem1.compare_to(&elem2)); + } + + #[test] + fn xmlns_wrong() { + let elem1: Element = "".parse().unwrap(); + let elem2: Element = "".parse().unwrap(); + assert!(!elem1.compare_to(&elem2)); + } + + #[test] + fn xmlns_other_prefix() { + let elem1: Element = "".parse().unwrap(); + let elem2: Element = "".parse().unwrap(); + assert!(elem1.compare_to(&elem2)); + } + + #[test] + fn xmlns_dup() { + let elem1: Element = "".parse().unwrap(); + let elem2: Element = "".parse().unwrap(); + assert!(elem1.compare_to(&elem2)); + } +} diff --git a/src/disco.rs b/src/disco.rs index b1d21680..e67e7973 100644 --- a/src/disco.rs +++ b/src/disco.rs @@ -370,6 +370,7 @@ impl From for Element { #[cfg(test)] mod tests { use super::*; + use compare_elements::NamespaceAwareCompare; use std::str::FromStr; #[test] @@ -394,7 +395,7 @@ mod tests { assert_eq!(query.extensions[0].form_type, Some(String::from("example"))); let elem2 = query.into(); - assert_eq!(elem1, elem2); + assert!(elem1.compare_to(&elem2)); } #[test] diff --git a/src/ibr.rs b/src/ibr.rs index c51153e6..05281f33 100644 --- a/src/ibr.rs +++ b/src/ibr.rs @@ -81,6 +81,7 @@ impl From for Element { #[cfg(test)] mod tests { use super::*; + use compare_elements::NamespaceAwareCompare; #[test] fn test_simple() { @@ -157,7 +158,7 @@ mod tests { assert!(form.fields.binary_search_by(|field| field.var.cmp(&String::from("x-gender"))).is_ok()); assert!(form.fields.binary_search_by(|field| field.var.cmp(&String::from("coucou"))).is_err()); let elem2 = query.into(); - assert_eq!(elem1, elem2); + assert!(elem1.compare_to(&elem2)); } #[test] @@ -190,6 +191,6 @@ mod tests { panic!(); } let elem2 = query.into(); - assert_eq!(elem1, elem2); + assert!(elem1.compare_to(&elem2)); } } diff --git a/src/iq.rs b/src/iq.rs index 7e919985..65c13a24 100644 --- a/src/iq.rs +++ b/src/iq.rs @@ -297,6 +297,7 @@ impl From for Element { mod tests { use super::*; use stanza_error::{ErrorType, DefinedCondition}; + use compare_elements::NamespaceAwareCompare; #[test] fn test_require_type() { @@ -328,7 +329,7 @@ mod tests { assert_eq!(iq.to, None); assert_eq!(iq.id, None); assert!(match iq.payload { - IqType::Get(element) => element == query, + IqType::Get(element) => element.compare_to(&query), _ => false }); } @@ -349,7 +350,7 @@ mod tests { assert_eq!(iq.to, None); assert_eq!(iq.id, None); assert!(match iq.payload { - IqType::Set(element) => element == vcard, + IqType::Set(element) => element.compare_to(&vcard), _ => false }); } @@ -386,7 +387,7 @@ mod tests { assert_eq!(iq.to, None); assert_eq!(iq.id, None); assert!(match iq.payload { - IqType::Result(Some(element)) => element == query, + IqType::Result(Some(element)) => element.compare_to(&query), _ => false, }); } diff --git a/src/jingle.rs b/src/jingle.rs index d36d0862..d20ed056 100644 --- a/src/jingle.rs +++ b/src/jingle.rs @@ -208,7 +208,8 @@ impl TryFrom for ReasonElement { let mut reason = None; let mut text = None; for child in elem.children() { - if child.ns() != Some(ns::JINGLE) { + let child_ns = child.ns(); + if child_ns.as_ref().map(|ns| ns.as_str()) != Some(ns::JINGLE) { return Err(Error::ParseError("Reason contains a foreign element.")); } match child.name() { diff --git a/src/jingle_message.rs b/src/jingle_message.rs index 216e1877..57cebe80 100644 --- a/src/jingle_message.rs +++ b/src/jingle_message.rs @@ -47,7 +47,8 @@ impl TryFrom for JingleMI { type Err = Error; fn try_from(elem: Element) -> Result { - if elem.ns() != Some(ns::JINGLE_MESSAGE) { + let ns = elem.ns(); + if ns.as_ref().map(|ns| ns.as_str()) != Some(ns::JINGLE_MESSAGE) { return Err(Error::ParseError("This is not a Jingle message element.")); } Ok(match elem.name() { diff --git a/src/jingle_s5b.rs b/src/jingle_s5b.rs index d454a3d9..6f8bb902 100644 --- a/src/jingle_s5b.rs +++ b/src/jingle_s5b.rs @@ -181,6 +181,7 @@ impl From for Element { #[cfg(test)] mod tests { use super::*; + use compare_elements::NamespaceAwareCompare; #[test] fn test_simple() { @@ -205,7 +206,7 @@ mod tests { payload: TransportPayload::Activated(String::from("coucou")), }; let elem2: Element = transport.into(); - assert_eq!(elem, elem2); + assert!(elem.compare_to(&elem2)); } #[test] @@ -225,6 +226,6 @@ mod tests { })), }; let elem2: Element = transport.into(); - assert_eq!(elem, elem2); + assert!(elem.compare_to(&elem2)); } } diff --git a/src/lib.rs b/src/lib.rs index cc9a8c31..75b092fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,6 +171,10 @@ pub mod error; /// XML namespace definitions used through XMPP. pub mod ns; +#[cfg(test)] +/// Namespace-aware comparison for tests +mod compare_elements; + /// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core pub mod message; /// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core diff --git a/src/message.rs b/src/message.rs index 5dba839e..3a4795b1 100644 --- a/src/message.rs +++ b/src/message.rs @@ -243,6 +243,7 @@ impl From for Element { #[cfg(test)] mod tests { use super::*; + use compare_elements::NamespaceAwareCompare; #[test] fn test_simple() { @@ -281,7 +282,7 @@ mod tests { assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap()); let elem2 = message.into(); - assert_eq!(elem1, elem2); + assert!(elem1.compare_to(&elem2)); } #[test] @@ -293,7 +294,7 @@ mod tests { let mut message = Message::new(Some(Jid::from_str("coucou@example.org").unwrap())); message.bodies.insert(String::from(""), Body::from_str("Hello world!").unwrap()); let elem2 = message.into(); - assert_eq!(elem, elem2); + assert!(elem.compare_to(&elem2)); } #[test] @@ -307,7 +308,7 @@ mod tests { assert_eq!(message.subjects[""], Subject::from_str("Hello world!").unwrap()); let elem2 = message.into(); - assert_eq!(elem1, elem2); + assert!(elem1.compare_to(&elem2)); } #[test] diff --git a/src/muc/user.rs b/src/muc/user.rs index 9e141ad2..322525cb 100644 --- a/src/muc/user.rs +++ b/src/muc/user.rs @@ -387,6 +387,7 @@ impl From for Element { mod tests { use super::*; use std::error::Error as StdError; + use compare_elements::NamespaceAwareCompare; #[test] fn test_simple() { @@ -418,7 +419,7 @@ mod tests { ".parse().unwrap(); let muc = MucUser { status: vec!(), items: vec!() }; let elem2 = muc.into(); - assert_eq!(elem, elem2); + assert!(elem.compare_to(&elem2)); } #[test] diff --git a/src/presence.rs b/src/presence.rs index a1f11fa6..e48e6850 100644 --- a/src/presence.rs +++ b/src/presence.rs @@ -340,6 +340,7 @@ impl From for Element { #[cfg(test)] mod tests { use super::*; + use compare_elements::NamespaceAwareCompare; #[test] fn test_simple() { @@ -363,7 +364,7 @@ mod tests { let elem: Element = "/>".parse().unwrap(); let presence = Presence::new(Type::Unavailable); let elem2 = presence.into(); - assert_eq!(elem, elem2); + assert!(elem.compare_to(&elem2)); } #[test] diff --git a/src/pubsub/event.rs b/src/pubsub/event.rs index 108d0ab1..d055476d 100644 --- a/src/pubsub/event.rs +++ b/src/pubsub/event.rs @@ -272,6 +272,7 @@ impl From for Element { mod tests { use super::*; use std::str::FromStr; + use compare_elements::NamespaceAwareCompare; #[test] fn test_simple() { @@ -417,6 +418,6 @@ mod tests { } let elem2: Element = event.into(); - assert_eq!(elem, elem2); + assert!(elem.compare_to(&elem2)); } } diff --git a/src/roster.rs b/src/roster.rs index 9e2bba31..ae26c3d2 100644 --- a/src/roster.rs +++ b/src/roster.rs @@ -137,6 +137,7 @@ impl From for Element { #[cfg(test)] mod tests { use super::*; + use compare_elements::NamespaceAwareCompare; #[test] fn test_get() { @@ -206,7 +207,7 @@ mod tests { assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap()); assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap()); let elem2 = roster.into(); - assert_eq!(elem1, elem2); + assert!(elem1.compare_to(&elem2)); } #[test] diff --git a/src/rsm.rs b/src/rsm.rs index fa6f6c81..97e7abc6 100644 --- a/src/rsm.rs +++ b/src/rsm.rs @@ -122,6 +122,7 @@ impl From for Element { #[cfg(test)] mod tests { use super::*; + use compare_elements::NamespaceAwareCompare; #[test] fn test_simple() { @@ -197,6 +198,6 @@ mod tests { max: None, }; let elem2 = set2.into(); - assert_eq!(elem1, elem2); + assert!(elem1.compare_to(&elem2)); } } diff --git a/src/stanza_error.rs b/src/stanza_error.rs index 8ca9c862..1ed542c1 100644 --- a/src/stanza_error.rs +++ b/src/stanza_error.rs @@ -136,6 +136,7 @@ impl TryFrom for StanzaError { let mut other = None; for child in elem.children() { + let child_ns = child.ns(); if child.is("text", ns::XMPP_STANZAS) { for _ in child.children() { return Err(Error::ParseError("Unknown element in error text.")); @@ -144,7 +145,7 @@ impl TryFrom for StanzaError { if texts.insert(lang, child.text()).is_some() { return Err(Error::ParseError("Text element present twice for the same xml:lang.")); } - } else if child.ns() == Some(ns::XMPP_STANZAS) { + } else if child_ns.as_ref().map(|ns| ns.as_str()) == Some(ns::XMPP_STANZAS) { if defined_condition.is_some() { return Err(Error::ParseError("Error must not have more than one defined-condition.")); }