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 of type Chat for the given recipient.
 98    /// This is equivalent to the [`Message::chat`] method.
 99    pub fn new<J: Into<Option<Jid>>>(to: J) -> Message {
100        Message {
101            from: None,
102            to: to.into(),
103            id: None,
104            type_: MessageType::Chat,
105            bodies: BTreeMap::new(),
106            subjects: BTreeMap::new(),
107            thread: None,
108            payloads: vec![],
109        }
110    }
111
112    /// Creates a new `<message/>` stanza of a certain type for the given recipient.
113    pub fn new_with_type<J: Into<Option<Jid>>>(type_: MessageType, to: J) -> Message {
114        Message {
115            from: None,
116            to: to.into(),
117            id: None,
118            type_,
119            bodies: BTreeMap::new(),
120            subjects: BTreeMap::new(),
121            thread: None,
122            payloads: vec![],
123        }
124    }
125
126    /// Creates a Message of type Chat
127    pub fn chat<J: Into<Option<Jid>>>(to: J) -> Message {
128        Self::new_with_type(MessageType::Chat, to)
129    }
130
131    /// Creates a Message of type Error
132    pub fn error<J: Into<Option<Jid>>>(to: J) -> Message {
133        Self::new_with_type(MessageType::Error, to)
134    }
135
136    /// Creates a Message of type Groupchat
137    pub fn groupchat<J: Into<Option<Jid>>>(to: J) -> Message {
138        Self::new_with_type(MessageType::Groupchat, to)
139    }
140
141    /// Creates a Message of type Headline
142    pub fn headline<J: Into<Option<Jid>>>(to: J) -> Message {
143        Self::new_with_type(MessageType::Headline, to)
144    }
145
146    /// Creates a Message of type Normal
147    pub fn normal<J: Into<Option<Jid>>>(to: J) -> Message {
148        Self::new_with_type(MessageType::Normal, to)
149    }
150
151    /// Appends a body in given lang to the Message
152    pub fn with_body(mut self, lang: Lang, body: String) -> Message {
153        self.bodies.insert(lang, Body(body));
154        self
155    }
156
157    /// Set a payload inside this message.
158    pub fn with_payload<P: MessagePayload>(mut self, payload: P) -> Message {
159        self.payloads.push(payload.into());
160        self
161    }
162
163    /// Set the payloads of this message.
164    pub fn with_payloads(mut self, payloads: Vec<Element>) -> Message {
165        self.payloads = payloads;
166        self
167    }
168
169    fn get_best<'a, T>(
170        map: &'a BTreeMap<Lang, T>,
171        preferred_langs: Vec<&str>,
172    ) -> Option<(Lang, &'a T)> {
173        if map.is_empty() {
174            return None;
175        }
176        for lang in preferred_langs {
177            if let Some(value) = map.get(lang) {
178                return Some((Lang::from(lang), value));
179            }
180        }
181        if let Some(value) = map.get("") {
182            return Some((Lang::new(), value));
183        }
184        map.iter().map(|(lang, value)| (lang.clone(), value)).next()
185    }
186
187    /// Returns the best matching body from a list of languages.
188    ///
189    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
190    /// body without an xml:lang attribute, and you pass ["fr", "en"] as your preferred languages,
191    /// `Some(("fr", the_second_body))` will be returned.
192    ///
193    /// If no body matches, an undefined body will be returned.
194    pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Body)> {
195        Message::get_best::<Body>(&self.bodies, preferred_langs)
196    }
197
198    /// Returns the best matching subject from a list of languages.
199    ///
200    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
201    /// subject without an xml:lang attribute, and you pass ["fr", "en"] as your preferred
202    /// languages, `Some(("fr", the_second_subject))` will be returned.
203    ///
204    /// If no subject matches, an undefined subject will be returned.
205    pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Subject)> {
206        Message::get_best::<Subject>(&self.subjects, preferred_langs)
207    }
208}
209
210impl TryFrom<Element> for Message {
211    type Error = Error;
212
213    fn try_from(root: Element) -> Result<Message, Error> {
214        check_self!(root, "message", DEFAULT_NS);
215        let from = get_attr!(root, "from", Option);
216        let to = get_attr!(root, "to", Option);
217        let id = get_attr!(root, "id", Option);
218        let type_ = get_attr!(root, "type", Default);
219        let mut bodies = BTreeMap::new();
220        let mut subjects = BTreeMap::new();
221        let mut thread = None;
222        let mut payloads = vec![];
223        for elem in root.children() {
224            if elem.is("body", ns::DEFAULT_NS) {
225                check_no_children!(elem, "body");
226                let lang = get_attr!(elem, "xml:lang", Default);
227                let body = Body(elem.text());
228                if bodies.insert(lang, body).is_some() {
229                    return Err(Error::ParseError(
230                        "Body element present twice for the same xml:lang.",
231                    ));
232                }
233            } else if elem.is("subject", ns::DEFAULT_NS) {
234                check_no_children!(elem, "subject");
235                let lang = get_attr!(elem, "xml:lang", Default);
236                let subject = Subject(elem.text());
237                if subjects.insert(lang, subject).is_some() {
238                    return Err(Error::ParseError(
239                        "Subject element present twice for the same xml:lang.",
240                    ));
241                }
242            } else if elem.is("thread", ns::DEFAULT_NS) {
243                if thread.is_some() {
244                    return Err(Error::ParseError("Thread element present twice."));
245                }
246                check_no_children!(elem, "thread");
247                thread = Some(Thread(elem.text()));
248            } else {
249                payloads.push(elem.clone())
250            }
251        }
252        Ok(Message {
253            from,
254            to,
255            id,
256            type_,
257            bodies,
258            subjects,
259            thread,
260            payloads,
261        })
262    }
263}
264
265impl From<Message> for Element {
266    fn from(message: Message) -> Element {
267        Element::builder("message", ns::DEFAULT_NS)
268            .attr("from", message.from)
269            .attr("to", message.to)
270            .attr("id", message.id)
271            .attr("type", message.type_)
272            .append_all(message.subjects.into_iter().map(|(lang, subject)| {
273                let mut subject = Element::from(subject);
274                subject.set_attr(
275                    "xml:lang",
276                    match lang.as_ref() {
277                        "" => None,
278                        lang => Some(lang),
279                    },
280                );
281                subject
282            }))
283            .append_all(message.bodies.into_iter().map(|(lang, body)| {
284                let mut body = Element::from(body);
285                body.set_attr(
286                    "xml:lang",
287                    match lang.as_ref() {
288                        "" => None,
289                        lang => Some(lang),
290                    },
291                );
292                body
293            }))
294            .append_all(message.payloads.into_iter())
295            .build()
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    use std::str::FromStr;
303
304    #[cfg(target_pointer_width = "32")]
305    #[test]
306    fn test_size() {
307        assert_size!(MessageType, 1);
308        assert_size!(Body, 12);
309        assert_size!(Subject, 12);
310        assert_size!(Thread, 12);
311        assert_size!(Message, 104);
312    }
313
314    #[cfg(target_pointer_width = "64")]
315    #[test]
316    fn test_size() {
317        assert_size!(MessageType, 1);
318        assert_size!(Body, 24);
319        assert_size!(Subject, 24);
320        assert_size!(Thread, 24);
321        assert_size!(Message, 208);
322    }
323
324    #[test]
325    fn test_simple() {
326        #[cfg(not(feature = "component"))]
327        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
328        #[cfg(feature = "component")]
329        let elem: Element = "<message xmlns='jabber:component:accept'/>"
330            .parse()
331            .unwrap();
332        let message = Message::try_from(elem).unwrap();
333        assert_eq!(message.from, None);
334        assert_eq!(message.to, None);
335        assert_eq!(message.id, None);
336        assert_eq!(message.type_, MessageType::Normal);
337        assert!(message.payloads.is_empty());
338    }
339
340    #[test]
341    fn test_serialise() {
342        #[cfg(not(feature = "component"))]
343        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
344        #[cfg(feature = "component")]
345        let elem: Element = "<message xmlns='jabber:component:accept'/>"
346            .parse()
347            .unwrap();
348        let mut message = Message::new(None);
349        message.type_ = MessageType::Normal;
350        let elem2 = message.into();
351        assert_eq!(elem, elem2);
352    }
353
354    #[test]
355    fn test_body() {
356        #[cfg(not(feature = "component"))]
357        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><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 elem1 = elem.clone();
361        let message = Message::try_from(elem).unwrap();
362        assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap());
363
364        {
365            let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
366            assert_eq!(lang, "");
367            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
368        }
369
370        let elem2 = message.into();
371        assert_eq!(elem1, elem2);
372    }
373
374    #[test]
375    fn test_serialise_body() {
376        #[cfg(not(feature = "component"))]
377        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
378        #[cfg(feature = "component")]
379        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
380        let mut message = Message::new(Jid::new("coucou@example.org").unwrap());
381        message
382            .bodies
383            .insert(String::from(""), Body::from_str("Hello world!").unwrap());
384        let elem2 = message.into();
385        assert_eq!(elem, elem2);
386    }
387
388    #[test]
389    fn test_subject() {
390        #[cfg(not(feature = "component"))]
391        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
392        #[cfg(feature = "component")]
393        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
394        let elem1 = elem.clone();
395        let message = Message::try_from(elem).unwrap();
396        assert_eq!(
397            message.subjects[""],
398            Subject::from_str("Hello world!").unwrap()
399        );
400
401        {
402            let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
403            assert_eq!(lang, "");
404            assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
405        }
406
407        let elem2 = message.into();
408        assert_eq!(elem1, elem2);
409    }
410
411    #[test]
412    fn get_best_body() {
413        #[cfg(not(feature = "component"))]
414        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();
415        #[cfg(feature = "component")]
416        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
417        let message = Message::try_from(elem).unwrap();
418
419        // Tests basic feature.
420        {
421            let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
422            assert_eq!(lang, "fr");
423            assert_eq!(body, &Body::from_str("Salut le monde !").unwrap());
424        }
425
426        // Tests order.
427        {
428            let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
429            assert_eq!(lang, "de");
430            assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
431        }
432
433        // Tests fallback.
434        {
435            let (lang, body) = message.get_best_body(vec![]).unwrap();
436            assert_eq!(lang, "");
437            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
438        }
439
440        // Tests fallback.
441        {
442            let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
443            assert_eq!(lang, "");
444            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
445        }
446
447        let message = Message::new(None);
448
449        // Tests without a body.
450        assert_eq!(message.get_best_body(vec!("ja")), None);
451    }
452
453    #[test]
454    fn test_attention() {
455        #[cfg(not(feature = "component"))]
456        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
457        #[cfg(feature = "component")]
458        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
459        let elem1 = elem.clone();
460        let message = Message::try_from(elem).unwrap();
461        let elem2 = message.into();
462        assert_eq!(elem1, elem2);
463    }
464}