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