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