1use core::fmt;
8use core::ops::{Deref, DerefMut};
9use std::borrow::{Borrow, Cow};
10
11use crate::ns;
12use alloc::collections::BTreeMap;
13use jid::Jid;
14use minidom::Element;
15use xso::{
16 error::{Error, FromElementError},
17 AsOptionalXmlText, AsXml, FromXml, FromXmlText,
18};
19
20pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
22
23generate_attribute!(
24 MessageType, "type", {
26 Chat => "chat",
28
29 Error => "error",
31
32 Groupchat => "groupchat",
34
35 Headline => "headline",
37
38 Normal => "normal",
41 }, Default = Normal
42);
43
44generate_id!(
45 Id
50);
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
57pub struct Lang(pub String);
58
59impl Deref for Lang {
60 type Target = String;
61
62 fn deref(&self) -> &Self::Target {
63 &self.0
64 }
65}
66
67impl DerefMut for Lang {
68 fn deref_mut(&mut self) -> &mut Self::Target {
69 &mut self.0
70 }
71}
72
73impl fmt::Display for Lang {
74 fn fmt<'f>(&self, f: &'f mut fmt::Formatter) -> fmt::Result {
75 fmt::Display::fmt(&self.0, f)
76 }
77}
78
79impl FromXmlText for Lang {
80 fn from_xml_text(s: String) -> Result<Self, Error> {
81 Ok(Self(s))
82 }
83}
84
85impl AsOptionalXmlText for Lang {
86 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, Error> {
87 if self.0.is_empty() {
88 Ok(None)
89 } else {
90 Ok(Some(Cow::Borrowed(&self.0)))
91 }
92 }
93}
94
95impl Borrow<str> for Lang {
96 fn borrow(&self) -> &str {
97 &self.0
98 }
99}
100
101impl From<String> for Lang {
102 fn from(other: String) -> Self {
103 Self(other)
104 }
105}
106
107impl From<&str> for Lang {
108 fn from(other: &str) -> Self {
109 Self(other.to_owned())
110 }
111}
112
113impl PartialEq<str> for Lang {
114 fn eq(&self, rhs: &str) -> bool {
115 self.0 == rhs
116 }
117}
118
119impl PartialEq<&str> for Lang {
120 fn eq(&self, rhs: &&str) -> bool {
121 self.0 == *rhs
122 }
123}
124
125impl Lang {
126 pub fn new() -> Self {
128 Self(String::new())
129 }
130}
131
132#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
134#[xml(namespace = ns::DEFAULT_NS, name = "thread")]
135pub struct Thread {
136 #[xml(attribute(default))]
138 pub parent: Option<String>,
139
140 #[xml(text)]
143 pub id: String,
144}
145
146#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
148#[xml(namespace = ns::DEFAULT_NS, name = "message")]
149pub struct Message {
150 #[xml(attribute(default))]
152 pub from: Option<Jid>,
153
154 #[xml(attribute(default))]
156 pub to: Option<Jid>,
157
158 #[xml(attribute(default))]
161 pub id: Option<Id>,
162
163 #[xml(attribute(name = "type", default))]
165 pub type_: MessageType,
166
167 #[xml(extract(n = .., name = "body", fields(
170 attribute(name = "xml:lang", type_ = Lang, default),
171 text(type_ = String),
172 )))]
173 pub bodies: BTreeMap<Lang, String>,
174
175 #[xml(extract(n = .., name = "subject", fields(
179 attribute(name = "xml:lang", type_ = Lang, default),
180 text(type_ = String),
181 )))]
182 pub subjects: BTreeMap<Lang, String>,
183
184 #[xml(child(default))]
187 pub thread: Option<Thread>,
188
189 #[xml(element(n = ..))]
191 pub payloads: Vec<Element>,
192}
193
194impl Message {
195 pub fn new<J: Into<Option<Jid>>>(to: J) -> Message {
198 Message {
199 from: None,
200 to: to.into(),
201 id: None,
202 type_: MessageType::Chat,
203 bodies: BTreeMap::new(),
204 subjects: BTreeMap::new(),
205 thread: None,
206 payloads: vec![],
207 }
208 }
209
210 pub fn new_with_type<J: Into<Option<Jid>>>(type_: MessageType, to: J) -> Message {
212 Message {
213 from: None,
214 to: to.into(),
215 id: None,
216 type_,
217 bodies: BTreeMap::new(),
218 subjects: BTreeMap::new(),
219 thread: None,
220 payloads: vec![],
221 }
222 }
223
224 pub fn chat<J: Into<Option<Jid>>>(to: J) -> Message {
226 Self::new_with_type(MessageType::Chat, to)
227 }
228
229 pub fn error<J: Into<Option<Jid>>>(to: J) -> Message {
231 Self::new_with_type(MessageType::Error, to)
232 }
233
234 pub fn groupchat<J: Into<Option<Jid>>>(to: J) -> Message {
236 Self::new_with_type(MessageType::Groupchat, to)
237 }
238
239 pub fn headline<J: Into<Option<Jid>>>(to: J) -> Message {
241 Self::new_with_type(MessageType::Headline, to)
242 }
243
244 pub fn normal<J: Into<Option<Jid>>>(to: J) -> Message {
246 Self::new_with_type(MessageType::Normal, to)
247 }
248
249 pub fn with_body(mut self, lang: Lang, body: String) -> Message {
251 self.bodies.insert(lang, body);
252 self
253 }
254
255 pub fn with_payload<P: MessagePayload>(mut self, payload: P) -> Message {
257 self.payloads.push(payload.into());
258 self
259 }
260
261 pub fn with_payloads(mut self, payloads: Vec<Element>) -> Message {
263 self.payloads = payloads;
264 self
265 }
266
267 fn get_best<'a, T>(
268 map: &'a BTreeMap<Lang, T>,
269 preferred_langs: Vec<&str>,
270 ) -> Option<(Lang, &'a T)> {
271 if map.is_empty() {
272 return None;
273 }
274 for lang in preferred_langs {
275 if let Some(value) = map.get(lang) {
276 return Some((Lang::from(lang), value));
277 }
278 }
279 if let Some(value) = map.get("") {
280 return Some((Lang::new(), value));
281 }
282 map.iter().map(|(lang, value)| (lang.clone(), value)).next()
283 }
284
285 fn get_best_cloned<T: ToOwned<Owned = T>>(
286 map: &BTreeMap<Lang, T>,
287 preferred_langs: Vec<&str>,
288 ) -> Option<(Lang, T)> {
289 if let Some((lang, item)) = Self::get_best::<T>(map, preferred_langs) {
290 Some((lang, item.to_owned()))
291 } else {
292 None
293 }
294 }
295
296 pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &String)> {
304 Message::get_best::<String>(&self.bodies, preferred_langs)
305 }
306
307 pub fn get_best_body_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, String)> {
309 Message::get_best_cloned::<String>(&self.bodies, preferred_langs)
310 }
311
312 pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &String)> {
320 Message::get_best::<String>(&self.subjects, preferred_langs)
321 }
322
323 pub fn get_best_subject_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, String)> {
325 Message::get_best_cloned::<String>(&self.subjects, preferred_langs)
326 }
327
328 pub fn extract_payload<T: TryFrom<Element, Error = FromElementError>>(
337 &mut self,
338 ) -> Result<Option<T>, Error> {
339 let mut buf = Vec::with_capacity(self.payloads.len());
340 let mut iter = self.payloads.drain(..);
341 let mut result = Ok(None);
342 for item in &mut iter {
343 match T::try_from(item) {
344 Ok(v) => {
345 result = Ok(Some(v));
346 break;
347 }
348 Err(FromElementError::Mismatch(residual)) => {
349 buf.push(residual);
350 }
351 Err(FromElementError::Invalid(other)) => {
352 result = Err(other);
353 break;
354 }
355 }
356 }
357 buf.extend(iter);
358 core::mem::swap(&mut buf, &mut self.payloads);
359 result
360 }
361
362 #[cfg(feature = "log")]
367 pub fn extract_valid_payload<T: TryFrom<Element, Error = FromElementError>>(
368 &mut self,
369 ) -> Option<T> {
370 match self.extract_payload::<T>() {
371 Ok(opt) => opt,
372 Err(e) => {
373 log::warn!("Failed to parse payload: {e}");
375 None
376 }
377 }
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[cfg(target_pointer_width = "32")]
386 #[test]
387 fn test_size() {
388 assert_size!(MessageType, 1);
389 assert_size!(Thread, 24);
390 assert_size!(Message, 108);
391 }
392
393 #[cfg(target_pointer_width = "64")]
394 #[test]
395 fn test_size() {
396 assert_size!(MessageType, 1);
397 assert_size!(Thread, 48);
398 assert_size!(Message, 216);
399 }
400
401 #[test]
402 fn test_simple() {
403 #[cfg(not(feature = "component"))]
404 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
405 #[cfg(feature = "component")]
406 let elem: Element = "<message xmlns='jabber:component:accept'/>"
407 .parse()
408 .unwrap();
409 let message = Message::try_from(elem).unwrap();
410 assert_eq!(message.from, None);
411 assert_eq!(message.to, None);
412 assert_eq!(message.id, None);
413 assert_eq!(message.type_, MessageType::Normal);
414 assert!(message.payloads.is_empty());
415 }
416
417 #[test]
418 fn test_serialise() {
419 #[cfg(not(feature = "component"))]
420 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
421 #[cfg(feature = "component")]
422 let elem: Element = "<message xmlns='jabber:component:accept'/>"
423 .parse()
424 .unwrap();
425 let mut message = Message::new(None);
426 message.type_ = MessageType::Normal;
427 let elem2 = message.into();
428 assert_eq!(elem, elem2);
429 }
430
431 #[test]
432 fn test_body() {
433 #[cfg(not(feature = "component"))]
434 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
435 #[cfg(feature = "component")]
436 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
437 let elem1 = elem.clone();
438 let message = Message::try_from(elem).unwrap();
439 assert_eq!(message.bodies[""], "Hello world!");
440
441 {
442 let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
443 assert_eq!(lang, "");
444 assert_eq!(body, &"Hello world!");
445 }
446
447 let elem2 = message.into();
448 assert_eq!(elem1, elem2);
449 }
450
451 #[test]
452 fn test_serialise_body() {
453 #[cfg(not(feature = "component"))]
454 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
455 #[cfg(feature = "component")]
456 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
457 let mut message = Message::new(Jid::new("coucou@example.org").unwrap());
458 message
459 .bodies
460 .insert(Lang::from(""), "Hello world!".to_owned());
461 let elem2 = message.into();
462 assert_eq!(elem, elem2);
463 }
464
465 #[test]
466 fn test_subject() {
467 #[cfg(not(feature = "component"))]
468 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
469 #[cfg(feature = "component")]
470 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
471 let elem1 = elem.clone();
472 let message = Message::try_from(elem).unwrap();
473 assert_eq!(message.subjects[""], "Hello world!",);
474
475 {
476 let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
477 assert_eq!(lang, "");
478 assert_eq!(subject, "Hello world!");
479 }
480
481 {
483 let (lang, subject) = message.get_best_subject_cloned(vec!["en"]).unwrap();
484 assert_eq!(lang, "");
485 assert_eq!(subject, "Hello world!");
486 }
487
488 let elem2 = message.into();
489 assert_eq!(elem1, elem2);
490 }
491
492 #[test]
493 fn get_best_body() {
494 #[cfg(not(feature = "component"))]
495 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();
496 #[cfg(feature = "component")]
497 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
498 let message = Message::try_from(elem).unwrap();
499
500 {
502 let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
503 assert_eq!(lang, "fr");
504 assert_eq!(body, "Salut le monde !");
505 }
506
507 {
509 let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
510 assert_eq!(lang, "de");
511 assert_eq!(body, "Hallo Welt!");
512 }
513
514 {
516 let (lang, body) = message.get_best_body(vec![]).unwrap();
517 assert_eq!(lang, "");
518 assert_eq!(body, "Hello world!");
519 }
520
521 {
523 let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
524 assert_eq!(lang, "");
525 assert_eq!(body, "Hello world!");
526 }
527
528 {
530 let (lang, body) = message.get_best_body_cloned(vec!["ja"]).unwrap();
531 assert_eq!(lang, "");
532 assert_eq!(body, "Hello world!");
533 }
534
535 let message = Message::new(None);
536
537 assert_eq!(message.get_best_body(vec!("ja")), None);
539 }
540
541 #[test]
542 fn test_attention() {
543 #[cfg(not(feature = "component"))]
544 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
545 #[cfg(feature = "component")]
546 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
547 let elem1 = elem.clone();
548 let message = Message::try_from(elem).unwrap();
549 let elem2 = message.into();
550 assert_eq!(elem1, elem2);
551 }
552
553 #[test]
554 fn test_extract_payload() {
555 use super::super::attention::Attention;
556 use super::super::pubsub;
557
558 #[cfg(not(feature = "component"))]
559 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
560 #[cfg(feature = "component")]
561 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
562 let mut message = Message::try_from(elem).unwrap();
563 assert_eq!(message.payloads.len(), 1);
564 match message.extract_payload::<pubsub::Event>() {
565 Ok(None) => (),
566 other => panic!("unexpected result: {:?}", other),
567 };
568 assert_eq!(message.payloads.len(), 1);
569 match message.extract_payload::<Attention>() {
570 Ok(Some(_)) => (),
571 other => panic!("unexpected result: {:?}", other),
572 };
573 assert_eq!(message.payloads.len(), 0);
574 }
575}