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::ns;
  8use crate::util::error::Error;
  9use crate::Element;
 10use jid::Jid;
 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, PartialEq)]
 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<J: Into<Option<Jid>>>(to: J) -> Message {
 99        Message {
100            from: None,
101            to: to.into(),
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", ns::DEFAULT_NS)
210            .attr("from", message.from)
211            .attr("to", message.to)
212            .attr("id", message.id)
213            .attr("type", message.type_)
214            .append_all(message.subjects.into_iter().map(|(lang, subject)| {
215                let mut subject = Element::from(subject);
216                subject.set_attr(
217                    "xml:lang",
218                    match lang.as_ref() {
219                        "" => None,
220                        lang => Some(lang),
221                    },
222                );
223                subject
224            }))
225            .append_all(message.bodies.into_iter().map(|(lang, body)| {
226                let mut body = Element::from(body);
227                body.set_attr(
228                    "xml:lang",
229                    match lang.as_ref() {
230                        "" => None,
231                        lang => Some(lang),
232                    },
233                );
234                body
235            }))
236            .append_all(message.payloads.into_iter())
237            .build()
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244    use jid::BareJid;
245    use std::str::FromStr;
246
247    #[cfg(target_pointer_width = "32")]
248    #[test]
249    fn test_size() {
250        assert_size!(MessageType, 1);
251        assert_size!(Body, 12);
252        assert_size!(Subject, 12);
253        assert_size!(Thread, 12);
254        assert_size!(Message, 144);
255    }
256
257    #[cfg(target_pointer_width = "64")]
258    #[test]
259    fn test_size() {
260        assert_size!(MessageType, 1);
261        assert_size!(Body, 24);
262        assert_size!(Subject, 24);
263        assert_size!(Thread, 24);
264        assert_size!(Message, 288);
265    }
266
267    #[test]
268    fn test_simple() {
269        #[cfg(not(feature = "component"))]
270        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
271        #[cfg(feature = "component")]
272        let elem: Element = "<message xmlns='jabber:component:accept'/>"
273            .parse()
274            .unwrap();
275        let message = Message::try_from(elem).unwrap();
276        assert_eq!(message.from, None);
277        assert_eq!(message.to, None);
278        assert_eq!(message.id, None);
279        assert_eq!(message.type_, MessageType::Normal);
280        assert!(message.payloads.is_empty());
281    }
282
283    #[test]
284    fn test_serialise() {
285        #[cfg(not(feature = "component"))]
286        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
287        #[cfg(feature = "component")]
288        let elem: Element = "<message xmlns='jabber:component:accept'/>"
289            .parse()
290            .unwrap();
291        let mut message = Message::new(None);
292        message.type_ = MessageType::Normal;
293        let elem2 = message.into();
294        assert_eq!(elem, elem2);
295    }
296
297    #[test]
298    fn test_body() {
299        #[cfg(not(feature = "component"))]
300        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
301        #[cfg(feature = "component")]
302        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
303        let elem1 = elem.clone();
304        let message = Message::try_from(elem).unwrap();
305        assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap());
306
307        {
308            let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
309            assert_eq!(lang, "");
310            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
311        }
312
313        let elem2 = message.into();
314        assert_eq!(elem1, elem2);
315    }
316
317    #[test]
318    fn test_serialise_body() {
319        #[cfg(not(feature = "component"))]
320        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><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 mut message = Message::new(Jid::Bare(BareJid::new("coucou", "example.org")));
324        message
325            .bodies
326            .insert(String::from(""), Body::from_str("Hello world!").unwrap());
327        let elem2 = message.into();
328        assert_eq!(elem, elem2);
329    }
330
331    #[test]
332    fn test_subject() {
333        #[cfg(not(feature = "component"))]
334        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
335        #[cfg(feature = "component")]
336        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
337        let elem1 = elem.clone();
338        let message = Message::try_from(elem).unwrap();
339        assert_eq!(
340            message.subjects[""],
341            Subject::from_str("Hello world!").unwrap()
342        );
343
344        {
345            let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
346            assert_eq!(lang, "");
347            assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
348        }
349
350        let elem2 = message.into();
351        assert_eq!(elem1, elem2);
352    }
353
354    #[test]
355    fn get_best_body() {
356        #[cfg(not(feature = "component"))]
357        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();
358        #[cfg(feature = "component")]
359        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
360        let message = Message::try_from(elem).unwrap();
361
362        // Tests basic feature.
363        {
364            let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
365            assert_eq!(lang, "fr");
366            assert_eq!(body, &Body::from_str("Salut le monde !").unwrap());
367        }
368
369        // Tests order.
370        {
371            let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
372            assert_eq!(lang, "de");
373            assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
374        }
375
376        // Tests fallback.
377        {
378            let (lang, body) = message.get_best_body(vec![]).unwrap();
379            assert_eq!(lang, "");
380            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
381        }
382
383        // Tests fallback.
384        {
385            let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
386            assert_eq!(lang, "");
387            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
388        }
389
390        let message = Message::new(None);
391
392        // Tests without a body.
393        assert_eq!(message.get_best_body(vec!("ja")), None);
394    }
395
396    #[test]
397    fn test_attention() {
398        #[cfg(not(feature = "component"))]
399        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
400        #[cfg(feature = "component")]
401        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
402        let elem1 = elem.clone();
403        let message = Message::try_from(elem).unwrap();
404        let elem2 = message.into();
405        assert_eq!(elem1, elem2);
406    }
407}