message: Merge body in this module, and make it support xml:lang.

Emmanuel Gil Peyrot created

Change summary

src/body.rs    | 86 ----------------------------------------------------
src/lib.rs     |  3 -
src/message.rs | 81 ++++++++++++++++++++++++++++++++----------------
3 files changed, 53 insertions(+), 117 deletions(-)

Detailed changes

src/body.rs 🔗

@@ -1,86 +0,0 @@
-// Copyright (c) 2017 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 minidom::Element;
-
-use error::Error;
-
-use ns;
-
-pub type Body = String;
-
-pub fn parse_body(root: &Element) -> Result<Body, Error> {
-    // TODO: also support components and servers.
-    if !root.is("body", ns::JABBER_CLIENT) {
-        return Err(Error::ParseError("This is not a body element."));
-    }
-    for _ in root.children() {
-        return Err(Error::ParseError("Unknown child in body element."));
-    }
-    Ok(root.text())
-}
-
-pub fn serialise(body: &Body) -> Element {
-    Element::builder("body")
-            .ns(ns::JABBER_CLIENT)
-            .append(body.to_owned())
-            .build()
-}
-
-#[cfg(test)]
-mod tests {
-    use minidom::Element;
-    use error::Error;
-    use body;
-    use ns;
-
-    #[test]
-    fn test_simple() {
-        let elem: Element = "<body xmlns='jabber:client'/>".parse().unwrap();
-        body::parse_body(&elem).unwrap();
-    }
-
-    #[test]
-    fn test_invalid() {
-        let elem: Element = "<body xmlns='jabber:server'/>".parse().unwrap();
-        let error = body::parse_body(&elem).unwrap_err();
-        let message = match error {
-            Error::ParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message, "This is not a body element.");
-    }
-
-    #[test]
-    fn test_invalid_child() {
-        let elem: Element = "<body xmlns='jabber:client'><coucou/></body>".parse().unwrap();
-        let error = body::parse_body(&elem).unwrap_err();
-        let message = match error {
-            Error::ParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message, "Unknown child in body element.");
-    }
-
-    #[test]
-    #[ignore]
-    fn test_invalid_attribute() {
-        let elem: Element = "<body xmlns='jabber:client' coucou=''/>".parse().unwrap();
-        let error = body::parse_body(&elem).unwrap_err();
-        let message = match error {
-            Error::ParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message, "Unknown attribute in body element.");
-    }
-
-    #[test]
-    fn test_serialise() {
-        let body = body::Body::from("Hello world!");
-        let elem = body::serialise(&body);
-        assert!(elem.is("body", ns::JABBER_CLIENT));
-    }
-}

src/lib.rs 🔗

@@ -37,9 +37,6 @@ pub mod iq;
 /// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core
 pub mod stanza_error;
 
-/// RFC 6121: Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence
-pub mod body;
-
 /// XEP-0004: Data Forms
 pub mod data_forms;
 

src/message.rs 🔗

@@ -6,6 +6,7 @@
 
 use std::convert::TryFrom;
 use std::str::FromStr;
+use std::collections::BTreeMap;
 
 use minidom::{Element, IntoAttributeValue};
 
@@ -15,7 +16,6 @@ use error::Error;
 
 use ns;
 
-use body;
 use stanza_error::StanzaError;
 use chatstates::ChatState;
 use receipts::Receipt;
@@ -27,7 +27,6 @@ use eme::ExplicitMessageEncryption;
 /// Lists every known payload of a `<message/>`.
 #[derive(Debug, Clone)]
 pub enum MessagePayload {
-    Body(body::Body),
     StanzaError(StanzaError),
     ChatState(ChatState),
     Receipt(Receipt),
@@ -86,12 +85,16 @@ pub enum MessagePayloadType {
     Parsed(MessagePayload),
 }
 
+type Lang = String;
+type Body = String;
+
 #[derive(Debug, Clone)]
 pub struct Message {
     pub from: Option<Jid>,
     pub to: Option<Jid>,
     pub id: Option<String>,
     pub type_: MessageType,
+    pub bodies: BTreeMap<Lang, Body>,
     pub payloads: Vec<MessagePayloadType>,
 }
 
@@ -112,37 +115,47 @@ impl<'a> TryFrom<&'a Element> for Message {
             Some(type_) => type_.parse()?,
             None => Default::default(),
         };
+        let mut bodies = BTreeMap::new();
         let mut payloads = vec!();
         for elem in root.children() {
-            let payload = if let Ok(body) = body::parse_body(elem) {
-                Some(MessagePayload::Body(body))
-            } else if let Ok(stanza_error) = StanzaError::try_from(elem) {
-                Some(MessagePayload::StanzaError(stanza_error))
-            } else if let Ok(chatstate) = ChatState::try_from(elem) {
-                Some(MessagePayload::ChatState(chatstate))
-            } else if let Ok(receipt) = Receipt::try_from(elem) {
-                Some(MessagePayload::Receipt(receipt))
-            } else if let Ok(delay) = Delay::try_from(elem) {
-                Some(MessagePayload::Delay(delay))
-            } else if let Ok(attention) = Attention::try_from(elem) {
-                Some(MessagePayload::Attention(attention))
-            } else if let Ok(replace) = Replace::try_from(elem) {
-                Some(MessagePayload::MessageCorrect(replace))
-            } else if let Ok(eme) = ExplicitMessageEncryption::try_from(elem) {
-                Some(MessagePayload::ExplicitMessageEncryption(eme))
+            if elem.is("body", ns::JABBER_CLIENT) {
+                for _ in elem.children() {
+                    return Err(Error::ParseError("Unknown child in body element."));
+                }
+                let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
+                if let Some(_) = bodies.insert(lang, elem.text()) {
+                    return Err(Error::ParseError("Body element present twice for the same xml:lang."));
+                }
             } else {
-                None
-            };
-            payloads.push(match payload {
-                Some(payload) => MessagePayloadType::Parsed(payload),
-                None => MessagePayloadType::XML(elem.clone()),
-            });
+                let payload = if let Ok(stanza_error) = StanzaError::try_from(elem) {
+                    Some(MessagePayload::StanzaError(stanza_error))
+                } else if let Ok(chatstate) = ChatState::try_from(elem) {
+                    Some(MessagePayload::ChatState(chatstate))
+                } else if let Ok(receipt) = Receipt::try_from(elem) {
+                    Some(MessagePayload::Receipt(receipt))
+                } else if let Ok(delay) = Delay::try_from(elem) {
+                    Some(MessagePayload::Delay(delay))
+                } else if let Ok(attention) = Attention::try_from(elem) {
+                    Some(MessagePayload::Attention(attention))
+                } else if let Ok(replace) = Replace::try_from(elem) {
+                    Some(MessagePayload::MessageCorrect(replace))
+                } else if let Ok(eme) = ExplicitMessageEncryption::try_from(elem) {
+                    Some(MessagePayload::ExplicitMessageEncryption(eme))
+                } else {
+                    None
+                };
+                payloads.push(match payload {
+                    Some(payload) => MessagePayloadType::Parsed(payload),
+                    None => MessagePayloadType::XML(elem.clone()),
+                });
+            }
         }
         Ok(Message {
             from: from,
             to: to,
             id: id,
             type_: type_,
+            bodies: BTreeMap::new(),
             payloads: payloads,
         })
     }
@@ -151,7 +164,6 @@ impl<'a> TryFrom<&'a Element> for Message {
 impl<'a> Into<Element> for &'a MessagePayload {
     fn into(self) -> Element {
         match *self {
-            MessagePayload::Body(ref body) => body::serialise(body),
             MessagePayload::StanzaError(ref stanza_error) => stanza_error.into(),
             MessagePayload::Attention(ref attention) => attention.into(),
             MessagePayload::ChatState(ref chatstate) => chatstate.into(),
@@ -171,6 +183,17 @@ impl<'a> Into<Element> for &'a Message {
                                  .attr("to", self.to.clone().and_then(|value| Some(String::from(value))))
                                  .attr("id", self.id.clone())
                                  .attr("type", self.type_.clone())
+                                 .append(self.bodies.iter()
+                                                    .map(|(lang, body)| {
+                                                         Element::builder("body")
+                                                                 .ns(ns::JABBER_CLIENT)
+                                                                 .attr("xml:lang", match lang.as_ref() {
+                                                                      "" => None,
+                                                                      lang => Some(lang),
+                                                                  })
+                                                                 .append(body.clone())
+                                                                 .build() })
+                                                    .collect::<Vec<_>>())
                                  .build();
         for child in self.payloads.clone() {
             let elem = match child {
@@ -206,6 +229,7 @@ mod tests {
             to: None,
             id: None,
             type_: MessageType::Normal,
+            bodies: BTreeMap::new(),
             payloads: vec!(),
         };
         let elem2 = (&message).into();
@@ -221,14 +245,15 @@ mod tests {
     #[test]
     fn test_serialise_body() {
         let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
+        let mut bodies = BTreeMap::new();
+        bodies.insert(String::from(""), String::from("Hello world!"));
         let message = Message {
             from: None,
             to: Some(Jid::from_str("coucou@example.org").unwrap()),
             id: None,
             type_: MessageType::Chat,
-            payloads: vec!(
-                MessagePayloadType::Parsed(MessagePayload::Body("Hello world!".to_owned())),
-            ),
+            bodies: bodies,
+            payloads: vec!(),
         };
         let elem2 = (&message).into();
         assert_eq!(elem, elem2);