message.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use try_from::TryFrom;
  8use std::collections::BTreeMap;
  9
 10use minidom::Element;
 11
 12use jid::Jid;
 13
 14use error::Error;
 15
 16use ns;
 17
 18/// Should be implemented on every known payload of a `<message/>`.
 19pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
 20
 21generate_attribute!(
 22    /// The type of a message.
 23    MessageType, "type", {
 24        /// Standard instant messaging message.
 25        Chat => "chat",
 26
 27        /// Notifies that an error happened.
 28        Error => "error",
 29
 30        /// Standard group instant messaging message.
 31        Groupchat => "groupchat",
 32
 33        /// Used by servers to notify users when things happen.
 34        Headline => "headline",
 35
 36        /// This is an email-like message, it usually contains a
 37        /// [subject](struct.Subject.html).
 38        Normal => "normal",
 39    }, Default = Normal
 40);
 41
 42type Lang = String;
 43
 44generate_elem_id!(
 45    /// Represents one `<body/>` element, that is the free form text content of
 46    /// a message.
 47    Body, "body", DEFAULT_NS
 48);
 49
 50generate_elem_id!(
 51    /// Defines the subject of a room, or of an email-like normal message.
 52    Subject, "subject", DEFAULT_NS
 53);
 54
 55generate_elem_id!(
 56    /// A thread identifier, so that other people can specify to which message
 57    /// they are replying.
 58    Thread, "thread", DEFAULT_NS
 59);
 60
 61/// The main structure representing the `<message/>` stanza.
 62#[derive(Debug, Clone)]
 63pub struct Message {
 64    /// The JID emitting this stanza.
 65    pub from: Option<Jid>,
 66
 67    /// The recipient of this stanza.
 68    pub to: Option<Jid>,
 69
 70    /// The @id attribute of this stanza, which is required in order to match a
 71    /// request with its response.
 72    pub id: Option<String>,
 73
 74    /// The type of this message.
 75    pub type_: MessageType,
 76
 77    /// A list of bodies, sorted per language.  Use
 78    /// [get_best_body()](#method.get_best_body) to access them on reception.
 79    pub bodies: BTreeMap<Lang, Body>,
 80
 81    /// A list of subjects, sorted per language.  Use
 82    /// [get_best_subject()](#method.get_best_subject) to access them on
 83    /// reception.
 84    pub subjects: BTreeMap<Lang, Subject>,
 85
 86    /// An optional thread identifier, so that other people can reply directly
 87    /// to this message.
 88    pub thread: Option<Thread>,
 89
 90    /// A list of the extension payloads contained in this stanza.
 91    pub payloads: Vec<Element>,
 92}
 93
 94impl Message {
 95    /// Creates a new `<message/>` stanza for the given recipient.
 96    pub fn new(to: Option<Jid>) -> Message {
 97        Message {
 98            from: None,
 99            to: to,
100            id: None,
101            type_: MessageType::Chat,
102            bodies: BTreeMap::new(),
103            subjects: BTreeMap::new(),
104            thread: None,
105            payloads: vec!(),
106        }
107    }
108
109    fn get_best<'a, T>(map: &'a BTreeMap<Lang, T>, preferred_langs: Vec<&str>) -> Option<(Lang, &'a T)> {
110        if map.is_empty() {
111            return None;
112        }
113        for lang in preferred_langs {
114            if let Some(value) = map.get(lang) {
115                return Some((Lang::from(lang), value));
116            }
117        }
118        if let Some(value) = map.get("") {
119            return Some((Lang::new(), value));
120        }
121        map.iter().map(|(lang, value)| (lang.clone(), value)).next()
122    }
123
124    /// Returns the best matching body from a list of languages.
125    ///
126    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
127    /// body without an xml:lang attribute, and you pass ["fr", "en"] as your preferred languages,
128    /// `Some(("fr", the_second_body))` will be returned.
129    ///
130    /// If no body matches, an undefined body will be returned.
131    pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Body)> {
132        Message::get_best::<Body>(&self.bodies, preferred_langs)
133    }
134
135    /// Returns the best matching subject from a list of languages.
136    ///
137    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
138    /// subject without an xml:lang attribute, and you pass ["fr", "en"] as your preferred
139    /// languages, `Some(("fr", the_second_subject))` will be returned.
140    ///
141    /// If no subject matches, an undefined subject will be returned.
142    pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Subject)> {
143        Message::get_best::<Subject>(&self.subjects, preferred_langs)
144    }
145}
146
147impl TryFrom<Element> for Message {
148    type Err = Error;
149
150    fn try_from(root: Element) -> Result<Message, Error> {
151        check_self!(root, "message", DEFAULT_NS);
152        let from = get_attr!(root, "from", optional);
153        let to = get_attr!(root, "to", optional);
154        let id = get_attr!(root, "id", optional);
155        let type_ = get_attr!(root, "type", default);
156        let mut bodies = BTreeMap::new();
157        let mut subjects = BTreeMap::new();
158        let mut thread = None;
159        let mut payloads = vec!();
160        for elem in root.children() {
161            if elem.is("body", ns::DEFAULT_NS) {
162                check_no_children!(elem, "body");
163                let lang = get_attr!(elem, "xml:lang", default);
164                let body = Body(elem.text());
165                if bodies.insert(lang, body).is_some() {
166                    return Err(Error::ParseError("Body element present twice for the same xml:lang."));
167                }
168            } else if elem.is("subject", ns::DEFAULT_NS) {
169                check_no_children!(elem, "subject");
170                let lang = get_attr!(elem, "xml:lang", default);
171                let subject = Subject(elem.text());
172                if subjects.insert(lang, subject).is_some() {
173                    return Err(Error::ParseError("Subject element present twice for the same xml:lang."));
174                }
175            } else if elem.is("thread", ns::DEFAULT_NS) {
176                if thread.is_some() {
177                    return Err(Error::ParseError("Thread element present twice."));
178                }
179                check_no_children!(elem, "thread");
180                thread = Some(Thread(elem.text()));
181            } else {
182                payloads.push(elem.clone())
183            }
184        }
185        Ok(Message {
186            from: from,
187            to: to,
188            id: id,
189            type_: type_,
190            bodies: bodies,
191            subjects: subjects,
192            thread: thread,
193            payloads: payloads,
194        })
195    }
196}
197
198impl From<Message> for Element {
199    fn from(message: Message) -> Element {
200        Element::builder("message")
201                .ns(ns::DEFAULT_NS)
202                .attr("from", message.from)
203                .attr("to", message.to)
204                .attr("id", message.id)
205                .attr("type", message.type_)
206                .append(message.subjects.into_iter()
207                                        .map(|(lang, subject)| {
208                                                 let mut subject = Element::from(subject);
209                                                 subject.set_attr("xml:lang", match lang.as_ref() {
210                                                     "" => None,
211                                                     lang => Some(lang),
212                                                 });
213                                                 subject
214                                             })
215                                        .collect::<Vec<_>>())
216                .append(message.bodies.into_iter()
217                                      .map(|(lang, body)| {
218                                               let mut body = Element::from(body);
219                                               body.set_attr("xml:lang", match lang.as_ref() {
220                                                   "" => None,
221                                                   lang => Some(lang),
222                                               });
223                                               body
224                                           })
225                                      .collect::<Vec<_>>())
226                .append(message.payloads)
227                .build()
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use std::str::FromStr;
235    use compare_elements::NamespaceAwareCompare;
236
237    #[test]
238    fn test_size() {
239        assert_size!(MessageType, 1);
240        assert_size!(Body, 24);
241        assert_size!(Subject, 24);
242        assert_size!(Thread, 24);
243        assert_size!(Message, 272);
244    }
245
246    #[test]
247    fn test_simple() {
248        #[cfg(not(feature = "component"))]
249        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
250        #[cfg(feature = "component")]
251        let elem: Element = "<message xmlns='jabber:component:accept'/>".parse().unwrap();
252        let message = Message::try_from(elem).unwrap();
253        assert_eq!(message.from, None);
254        assert_eq!(message.to, None);
255        assert_eq!(message.id, None);
256        assert_eq!(message.type_, MessageType::Normal);
257        assert!(message.payloads.is_empty());
258    }
259
260    #[test]
261    fn test_serialise() {
262        #[cfg(not(feature = "component"))]
263        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
264        #[cfg(feature = "component")]
265        let elem: Element = "<message xmlns='jabber:component:accept'/>".parse().unwrap();
266        let mut message = Message::new(None);
267        message.type_ = MessageType::Normal;
268        let elem2 = message.into();
269        assert_eq!(elem, elem2);
270    }
271
272    #[test]
273    fn test_body() {
274        #[cfg(not(feature = "component"))]
275        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
276        #[cfg(feature = "component")]
277        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
278        let elem1 = elem.clone();
279        let message = Message::try_from(elem).unwrap();
280        assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap());
281
282        {
283            let (lang, body) = message.get_best_body(vec!("en")).unwrap();
284            assert_eq!(lang, "");
285            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
286        }
287
288        let elem2 = message.into();
289        assert!(elem1.compare_to(&elem2));
290    }
291
292    #[test]
293    fn test_serialise_body() {
294        #[cfg(not(feature = "component"))]
295        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
296        #[cfg(feature = "component")]
297        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
298        let mut message = Message::new(Some(Jid::from_str("coucou@example.org").unwrap()));
299        message.bodies.insert(String::from(""), Body::from_str("Hello world!").unwrap());
300        let elem2 = message.into();
301        assert!(elem.compare_to(&elem2));
302    }
303
304    #[test]
305    fn test_subject() {
306        #[cfg(not(feature = "component"))]
307        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
308        #[cfg(feature = "component")]
309        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
310        let elem1 = elem.clone();
311        let message = Message::try_from(elem).unwrap();
312        assert_eq!(message.subjects[""], Subject::from_str("Hello world!").unwrap());
313
314        {
315            let (lang, subject) = message.get_best_subject(vec!("en")).unwrap();
316            assert_eq!(lang, "");
317            assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
318        }
319
320        let elem2 = message.into();
321        assert!(elem1.compare_to(&elem2));
322    }
323
324    #[test]
325    fn get_best_body() {
326        #[cfg(not(feature = "component"))]
327        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body xml:lang='de'>Hallo Welt!</body><body xml:lang='fr'>Salut le monde !</body><body>Hello world!</body></message>".parse().unwrap();
328        #[cfg(feature = "component")]
329        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
330        let message = Message::try_from(elem).unwrap();
331
332        // Tests basic feature.
333        {
334            let (lang, body) = message.get_best_body(vec!("fr")).unwrap();
335            assert_eq!(lang, "fr");
336            assert_eq!(body, &Body::from_str("Salut le monde !").unwrap());
337        }
338
339        // Tests order.
340        {
341            let (lang, body) = message.get_best_body(vec!("en", "de")).unwrap();
342            assert_eq!(lang, "de");
343            assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
344        }
345
346        // Tests fallback.
347        {
348            let (lang, body) = message.get_best_body(vec!()).unwrap();
349            assert_eq!(lang, "");
350            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
351        }
352
353        // Tests fallback.
354        {
355            let (lang, body) = message.get_best_body(vec!("ja")).unwrap();
356            assert_eq!(lang, "");
357            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
358        }
359
360        let message = Message::new(None);
361
362        // Tests without a body.
363        assert_eq!(message.get_best_body(vec!("ja")), None);
364    }
365
366    #[test]
367    fn test_attention() {
368        #[cfg(not(feature = "component"))]
369        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
370        #[cfg(feature = "component")]
371        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
372        let elem1 = elem.clone();
373        let message = Message::try_from(elem).unwrap();
374        let elem2 = message.into();
375        assert_eq!(elem1, elem2);
376    }
377}