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 crate::util::error::Error;
  8use crate::ns;
  9use jid::Jid;
 10use crate::Element;
 11use std::collections::BTreeMap;
 12use std::convert::TryFrom;
 13
 14/// Should be implemented on every known payload of a `<message/>`.
 15pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
 16
 17generate_attribute!(
 18    /// The type of a message.
 19    MessageType, "type", {
 20        /// Standard instant messaging message.
 21        Chat => "chat",
 22
 23        /// Notifies that an error happened.
 24        Error => "error",
 25
 26        /// Standard group instant messaging message.
 27        Groupchat => "groupchat",
 28
 29        /// Used by servers to notify users when things happen.
 30        Headline => "headline",
 31
 32        /// This is an email-like message, it usually contains a
 33        /// [subject](struct.Subject.html).
 34        Normal => "normal",
 35    }, Default = Normal
 36);
 37
 38type Lang = String;
 39
 40generate_elem_id!(
 41    /// Represents one `<body/>` element, that is the free form text content of
 42    /// a message.
 43    Body,
 44    "body",
 45    DEFAULT_NS
 46);
 47
 48generate_elem_id!(
 49    /// Defines the subject of a room, or of an email-like normal message.
 50    Subject,
 51    "subject",
 52    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,
 59    "thread",
 60    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,
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>(
112        map: &'a BTreeMap<Lang, T>,
113        preferred_langs: Vec<&str>,
114    ) -> Option<(Lang, &'a T)> {
115        if map.is_empty() {
116            return None;
117        }
118        for lang in preferred_langs {
119            if let Some(value) = map.get(lang) {
120                return Some((Lang::from(lang), value));
121            }
122        }
123        if let Some(value) = map.get("") {
124            return Some((Lang::new(), value));
125        }
126        map.iter().map(|(lang, value)| (lang.clone(), value)).next()
127    }
128
129    /// Returns the best matching body from a list of languages.
130    ///
131    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
132    /// body without an xml:lang attribute, and you pass ["fr", "en"] as your preferred languages,
133    /// `Some(("fr", the_second_body))` will be returned.
134    ///
135    /// If no body matches, an undefined body will be returned.
136    pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Body)> {
137        Message::get_best::<Body>(&self.bodies, preferred_langs)
138    }
139
140    /// Returns the best matching subject from a list of languages.
141    ///
142    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
143    /// subject without an xml:lang attribute, and you pass ["fr", "en"] as your preferred
144    /// languages, `Some(("fr", the_second_subject))` will be returned.
145    ///
146    /// If no subject matches, an undefined subject will be returned.
147    pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Subject)> {
148        Message::get_best::<Subject>(&self.subjects, preferred_langs)
149    }
150}
151
152impl TryFrom<Element> for Message {
153    type Error = Error;
154
155    fn try_from(root: Element) -> Result<Message, Error> {
156        check_self!(root, "message", DEFAULT_NS);
157        let from = get_attr!(root, "from", Option);
158        let to = get_attr!(root, "to", Option);
159        let id = get_attr!(root, "id", Option);
160        let type_ = get_attr!(root, "type", Default);
161        let mut bodies = BTreeMap::new();
162        let mut subjects = BTreeMap::new();
163        let mut thread = None;
164        let mut payloads = vec![];
165        for elem in root.children() {
166            if elem.is("body", ns::DEFAULT_NS) {
167                check_no_children!(elem, "body");
168                let lang = get_attr!(elem, "xml:lang", Default);
169                let body = Body(elem.text());
170                if bodies.insert(lang, body).is_some() {
171                    return Err(Error::ParseError(
172                        "Body element present twice for the same xml:lang.",
173                    ));
174                }
175            } else if elem.is("subject", ns::DEFAULT_NS) {
176                check_no_children!(elem, "subject");
177                let lang = get_attr!(elem, "xml:lang", Default);
178                let subject = Subject(elem.text());
179                if subjects.insert(lang, subject).is_some() {
180                    return Err(Error::ParseError(
181                        "Subject element present twice for the same xml:lang.",
182                    ));
183                }
184            } else if elem.is("thread", ns::DEFAULT_NS) {
185                if thread.is_some() {
186                    return Err(Error::ParseError("Thread element present twice."));
187                }
188                check_no_children!(elem, "thread");
189                thread = Some(Thread(elem.text()));
190            } else {
191                payloads.push(elem.clone())
192            }
193        }
194        Ok(Message {
195            from,
196            to,
197            id,
198            type_,
199            bodies,
200            subjects,
201            thread,
202            payloads,
203        })
204    }
205}
206
207impl From<Message> for Element {
208    fn from(message: Message) -> Element {
209        Element::builder("message")
210            .ns(ns::DEFAULT_NS)
211            .attr("from", message.from)
212            .attr("to", message.to)
213            .attr("id", message.id)
214            .attr("type", message.type_)
215            .append_all(
216                message
217                    .subjects
218                    .into_iter()
219                    .map(|(lang, subject)| {
220                        let mut subject = Element::from(subject);
221                        subject.set_attr(
222                            "xml:lang",
223                            match lang.as_ref() {
224                                "" => None,
225                                lang => Some(lang),
226                            },
227                        );
228                        subject
229                    })
230            )
231            .append_all(
232                message
233                    .bodies
234                    .into_iter()
235                    .map(|(lang, body)| {
236                        let mut body = Element::from(body);
237                        body.set_attr(
238                            "xml:lang",
239                            match lang.as_ref() {
240                                "" => None,
241                                lang => Some(lang),
242                            },
243                        );
244                        body
245                    })
246            )
247            .append_all(message.payloads.into_iter())
248            .build()
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use crate::util::compare_elements::NamespaceAwareCompare;
256    use std::str::FromStr;
257
258    #[cfg(target_pointer_width = "32")]
259    #[test]
260    fn test_size() {
261        assert_size!(MessageType, 1);
262        assert_size!(Body, 12);
263        assert_size!(Subject, 12);
264        assert_size!(Thread, 12);
265        assert_size!(Message, 144);
266    }
267
268    #[cfg(target_pointer_width = "64")]
269    #[test]
270    fn test_size() {
271        assert_size!(MessageType, 1);
272        assert_size!(Body, 24);
273        assert_size!(Subject, 24);
274        assert_size!(Thread, 24);
275        assert_size!(Message, 288);
276    }
277
278    #[test]
279    fn test_simple() {
280        #[cfg(not(feature = "component"))]
281        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
282        #[cfg(feature = "component")]
283        let elem: Element = "<message xmlns='jabber:component:accept'/>"
284            .parse()
285            .unwrap();
286        let message = Message::try_from(elem).unwrap();
287        assert_eq!(message.from, None);
288        assert_eq!(message.to, None);
289        assert_eq!(message.id, None);
290        assert_eq!(message.type_, MessageType::Normal);
291        assert!(message.payloads.is_empty());
292    }
293
294    #[test]
295    fn test_serialise() {
296        #[cfg(not(feature = "component"))]
297        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
298        #[cfg(feature = "component")]
299        let elem: Element = "<message xmlns='jabber:component:accept'/>"
300            .parse()
301            .unwrap();
302        let mut message = Message::new(None);
303        message.type_ = MessageType::Normal;
304        let elem2 = message.into();
305        assert_eq!(elem, elem2);
306    }
307
308    #[test]
309    fn test_body() {
310        #[cfg(not(feature = "component"))]
311        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
312        #[cfg(feature = "component")]
313        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
314        let elem1 = elem.clone();
315        let message = Message::try_from(elem).unwrap();
316        assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap());
317
318        {
319            let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
320            assert_eq!(lang, "");
321            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
322        }
323
324        let elem2 = message.into();
325        assert!(elem1.compare_to(&elem2));
326    }
327
328    #[test]
329    fn test_serialise_body() {
330        #[cfg(not(feature = "component"))]
331        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
332        #[cfg(feature = "component")]
333        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
334        let mut message = Message::new(Some(Jid::from_str("coucou@example.org").unwrap()));
335        message
336            .bodies
337            .insert(String::from(""), Body::from_str("Hello world!").unwrap());
338        let elem2 = message.into();
339        assert!(elem.compare_to(&elem2));
340    }
341
342    #[test]
343    fn test_subject() {
344        #[cfg(not(feature = "component"))]
345        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
346        #[cfg(feature = "component")]
347        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
348        let elem1 = elem.clone();
349        let message = Message::try_from(elem).unwrap();
350        assert_eq!(
351            message.subjects[""],
352            Subject::from_str("Hello world!").unwrap()
353        );
354
355        {
356            let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
357            assert_eq!(lang, "");
358            assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
359        }
360
361        let elem2 = message.into();
362        assert!(elem1.compare_to(&elem2));
363    }
364
365    #[test]
366    fn get_best_body() {
367        #[cfg(not(feature = "component"))]
368        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();
369        #[cfg(feature = "component")]
370        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
371        let message = Message::try_from(elem).unwrap();
372
373        // Tests basic feature.
374        {
375            let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
376            assert_eq!(lang, "fr");
377            assert_eq!(body, &Body::from_str("Salut le monde !").unwrap());
378        }
379
380        // Tests order.
381        {
382            let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
383            assert_eq!(lang, "de");
384            assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
385        }
386
387        // Tests fallback.
388        {
389            let (lang, body) = message.get_best_body(vec![]).unwrap();
390            assert_eq!(lang, "");
391            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
392        }
393
394        // Tests fallback.
395        {
396            let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
397            assert_eq!(lang, "");
398            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
399        }
400
401        let message = Message::new(None);
402
403        // Tests without a body.
404        assert_eq!(message.get_best_body(vec!("ja")), None);
405    }
406
407    #[test]
408    fn test_attention() {
409        #[cfg(not(feature = "component"))]
410        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
411        #[cfg(feature = "component")]
412        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
413        let elem1 = elem.clone();
414        let message = Message::try_from(elem).unwrap();
415        let elem2 = message.into();
416        assert_eq!(elem1, elem2);
417    }
418}