Introduce comparing with namespace support.

Astro created

Change summary

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(-)

Detailed changes

src/chatstates.rs 🔗

@@ -38,7 +38,8 @@ impl TryFrom<Element> for ChatState {
     type Err = Error;
 
     fn try_from(elem: Element) -> Result<ChatState, Error> {
-        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() {

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 = "<a a='b'>x <l/> 3</a>".parse().unwrap();
+        let elem2: Element = "<a a='b'>x <l/> 3</a>".parse().unwrap();
+        assert!(elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn wrong_attr_name() {
+        let elem1: Element = "<a a='b'>x 3</a>".parse().unwrap();
+        let elem2: Element = "<a c='b'>x 3</a>".parse().unwrap();
+        assert!(!elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn wrong_attr_value() {
+        let elem1: Element = "<a a='b'>x 3</a>".parse().unwrap();
+        let elem2: Element = "<a a='c'>x 3</a>".parse().unwrap();
+        assert!(!elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn attr_order() {
+        let elem1: Element = "<e1 a='b' c='d'/>".parse().unwrap();
+        let elem2: Element = "<e1 c='d' a='b'/>".parse().unwrap();
+        assert!(elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn wrong_texts() {
+        let elem1: Element = "<e1>foo</e1>".parse().unwrap();
+        let elem2: Element = "<e1>bar</e1>".parse().unwrap();
+        assert!(!elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn children() {
+        let elem1: Element = "<e1><foo/><bar/></e1>".parse().unwrap();
+        let elem2: Element = "<e1><foo/><bar/></e1>".parse().unwrap();
+        assert!(elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn wrong_children() {
+        let elem1: Element = "<e1><foo/></e1>".parse().unwrap();
+        let elem2: Element = "<e1><bar/></e1>".parse().unwrap();
+        assert!(!elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn xmlns_wrong() {
+        let elem1: Element = "<e1 xmlns='ns1'><foo/></e1>".parse().unwrap();
+        let elem2: Element = "<e1 xmlns='ns2'><foo/></e1>".parse().unwrap();
+        assert!(!elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn xmlns_other_prefix() {
+        let elem1: Element = "<e1 xmlns='ns1'><foo/></e1>".parse().unwrap();
+        let elem2: Element = "<x:e1 xmlns:x='ns1'><x:foo/></x:e1>".parse().unwrap();
+        assert!(elem1.compare_to(&elem2));
+    }
+
+    #[test]
+    fn xmlns_dup() {
+        let elem1: Element = "<e1 xmlns='ns1'><foo/></e1>".parse().unwrap();
+        let elem2: Element = "<e1 xmlns='ns1'><foo  xmlns='ns1'/></e1>".parse().unwrap();
+        assert!(elem1.compare_to(&elem2));
+    }
+}

src/disco.rs 🔗

@@ -370,6 +370,7 @@ impl From<DiscoItemsResult> 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]

src/ibr.rs 🔗

@@ -81,6 +81,7 @@ impl From<Query> 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));
     }
 }

src/iq.rs 🔗

@@ -297,6 +297,7 @@ impl From<Iq> 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,
         });
     }

src/jingle.rs 🔗

@@ -208,7 +208,8 @@ impl TryFrom<Element> 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() {

src/jingle_message.rs 🔗

@@ -47,7 +47,8 @@ impl TryFrom<Element> for JingleMI {
     type Err = Error;
 
     fn try_from(elem: Element) -> Result<JingleMI, Error> {
-        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() {

src/jingle_s5b.rs 🔗

@@ -181,6 +181,7 @@ impl From<Transport> 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));
     }
 }

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

src/message.rs 🔗

@@ -243,6 +243,7 @@ impl From<Message> 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]

src/muc/user.rs 🔗

@@ -387,6 +387,7 @@ impl From<MucUser> 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]

src/presence.rs 🔗

@@ -340,6 +340,7 @@ impl From<Presence> 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 = "<presence xmlns='jabber:component:accept' type='unavailable'/>/>".parse().unwrap();
         let presence = Presence::new(Type::Unavailable);
         let elem2 = presence.into();
-        assert_eq!(elem, elem2);
+        assert!(elem.compare_to(&elem2));
     }
 
     #[test]

src/pubsub/event.rs 🔗

@@ -272,6 +272,7 @@ impl From<PubSubEvent> 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));
     }
 }

src/roster.rs 🔗

@@ -137,6 +137,7 @@ impl From<Roster> 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]

src/rsm.rs 🔗

@@ -122,6 +122,7 @@ impl From<Set> 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));
     }
 }

src/stanza_error.rs 🔗

@@ -136,6 +136,7 @@ impl TryFrom<Element> 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<Element> 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."));
                 }