1use crate::ns;
8use alloc::collections::BTreeMap;
9use jid::Jid;
10use minidom::Element;
11use xso::error::{Error, FromElementError};
12
13pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
15
16generate_attribute!(
17 MessageType, "type", {
19 Chat => "chat",
21
22 Error => "error",
24
25 Groupchat => "groupchat",
27
28 Headline => "headline",
30
31 Normal => "normal",
34 }, Default = Normal
35);
36
37type Lang = String;
38
39generate_id!(
40 Id
45);
46
47generate_elem_id!(
48 Body,
51 "body",
52 DEFAULT_NS
53);
54
55generate_elem_id!(
56 Subject,
58 "subject",
59 DEFAULT_NS
60);
61
62generate_elem_id!(
63 Thread,
66 "thread",
67 DEFAULT_NS
68);
69
70#[derive(Debug, Clone, PartialEq)]
72pub struct Message {
73 pub from: Option<Jid>,
75
76 pub to: Option<Jid>,
78
79 pub id: Option<Id>,
82
83 pub type_: MessageType,
85
86 pub bodies: BTreeMap<Lang, Body>,
89
90 pub subjects: BTreeMap<Lang, Subject>,
94
95 pub thread: Option<Thread>,
98
99 pub payloads: Vec<Element>,
101}
102
103impl Message {
104 pub fn new<J: Into<Option<Jid>>>(to: J) -> Message {
107 Message {
108 from: None,
109 to: to.into(),
110 id: None,
111 type_: MessageType::Chat,
112 bodies: BTreeMap::new(),
113 subjects: BTreeMap::new(),
114 thread: None,
115 payloads: vec![],
116 }
117 }
118
119 pub fn new_with_type<J: Into<Option<Jid>>>(type_: MessageType, to: J) -> Message {
121 Message {
122 from: None,
123 to: to.into(),
124 id: None,
125 type_,
126 bodies: BTreeMap::new(),
127 subjects: BTreeMap::new(),
128 thread: None,
129 payloads: vec![],
130 }
131 }
132
133 pub fn chat<J: Into<Option<Jid>>>(to: J) -> Message {
135 Self::new_with_type(MessageType::Chat, to)
136 }
137
138 pub fn error<J: Into<Option<Jid>>>(to: J) -> Message {
140 Self::new_with_type(MessageType::Error, to)
141 }
142
143 pub fn groupchat<J: Into<Option<Jid>>>(to: J) -> Message {
145 Self::new_with_type(MessageType::Groupchat, to)
146 }
147
148 pub fn headline<J: Into<Option<Jid>>>(to: J) -> Message {
150 Self::new_with_type(MessageType::Headline, to)
151 }
152
153 pub fn normal<J: Into<Option<Jid>>>(to: J) -> Message {
155 Self::new_with_type(MessageType::Normal, to)
156 }
157
158 pub fn with_body(mut self, lang: Lang, body: String) -> Message {
160 self.bodies.insert(lang, Body(body));
161 self
162 }
163
164 pub fn with_payload<P: MessagePayload>(mut self, payload: P) -> Message {
166 self.payloads.push(payload.into());
167 self
168 }
169
170 pub fn with_payloads(mut self, payloads: Vec<Element>) -> Message {
172 self.payloads = payloads;
173 self
174 }
175
176 fn get_best<'a, T>(
177 map: &'a BTreeMap<Lang, T>,
178 preferred_langs: Vec<&str>,
179 ) -> Option<(Lang, &'a T)> {
180 if map.is_empty() {
181 return None;
182 }
183 for lang in preferred_langs {
184 if let Some(value) = map.get(lang) {
185 return Some((Lang::from(lang), value));
186 }
187 }
188 if let Some(value) = map.get("") {
189 return Some((Lang::new(), value));
190 }
191 map.iter().map(|(lang, value)| (lang.clone(), value)).next()
192 }
193
194 fn get_best_cloned<T: ToOwned<Owned = T>>(
195 map: &BTreeMap<Lang, T>,
196 preferred_langs: Vec<&str>,
197 ) -> Option<(Lang, T)> {
198 if let Some((lang, item)) = Self::get_best::<T>(map, preferred_langs) {
199 Some((lang, item.to_owned()))
200 } else {
201 None
202 }
203 }
204
205 pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Body)> {
213 Message::get_best::<Body>(&self.bodies, preferred_langs)
214 }
215
216 pub fn get_best_body_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, Body)> {
218 Message::get_best_cloned::<Body>(&self.bodies, preferred_langs)
219 }
220
221 pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Subject)> {
229 Message::get_best::<Subject>(&self.subjects, preferred_langs)
230 }
231
232 pub fn get_best_subject_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, Subject)> {
234 Message::get_best_cloned::<Subject>(&self.subjects, preferred_langs)
235 }
236
237 pub fn extract_payload<T: TryFrom<Element, Error = FromElementError>>(
246 &mut self,
247 ) -> Result<Option<T>, Error> {
248 let mut buf = Vec::with_capacity(self.payloads.len());
249 let mut iter = self.payloads.drain(..);
250 let mut result = Ok(None);
251 for item in &mut iter {
252 match T::try_from(item) {
253 Ok(v) => {
254 result = Ok(Some(v));
255 break;
256 }
257 Err(FromElementError::Mismatch(residual)) => {
258 buf.push(residual);
259 }
260 Err(FromElementError::Invalid(other)) => {
261 result = Err(other);
262 break;
263 }
264 }
265 }
266 buf.extend(iter);
267 core::mem::swap(&mut buf, &mut self.payloads);
268 result
269 }
270
271 #[cfg(feature = "log")]
276 pub fn extract_valid_payload<T: TryFrom<Element, Error = FromElementError>>(
277 &mut self,
278 ) -> Option<T> {
279 match self.extract_payload::<T>() {
280 Ok(opt) => opt,
281 Err(e) => {
282 log::warn!("Failed to parse payload: {e}");
284 None
285 }
286 }
287 }
288}
289
290impl TryFrom<Element> for Message {
291 type Error = FromElementError;
292
293 fn try_from(root: Element) -> Result<Message, FromElementError> {
294 check_self!(root, "message", DEFAULT_NS);
295 let from = get_attr!(root, "from", Option);
296 let to = get_attr!(root, "to", Option);
297 let id = get_attr!(root, "id", Option);
298 let type_ = get_attr!(root, "type", Default);
299 let mut bodies = BTreeMap::new();
300 let mut subjects = BTreeMap::new();
301 let mut thread = None;
302 let mut payloads = vec![];
303 for elem in root.children() {
304 if elem.is("body", ns::DEFAULT_NS) {
305 check_no_children!(elem, "body");
306 let lang = get_attr!(elem, "xml:lang", Default);
307 let body = Body(elem.text());
308 if bodies.insert(lang, body).is_some() {
309 return Err(
310 Error::Other("Body element present twice for the same xml:lang.").into(),
311 );
312 }
313 } else if elem.is("subject", ns::DEFAULT_NS) {
314 check_no_children!(elem, "subject");
315 let lang = get_attr!(elem, "xml:lang", Default);
316 let subject = Subject(elem.text());
317 if subjects.insert(lang, subject).is_some() {
318 return Err(Error::Other(
319 "Subject element present twice for the same xml:lang.",
320 )
321 .into());
322 }
323 } else if elem.is("thread", ns::DEFAULT_NS) {
324 if thread.is_some() {
325 return Err(Error::Other("Thread element present twice.").into());
326 }
327 check_no_children!(elem, "thread");
328 thread = Some(Thread(elem.text()));
329 } else {
330 payloads.push(elem.clone())
331 }
332 }
333 Ok(Message {
334 from,
335 to,
336 id,
337 type_,
338 bodies,
339 subjects,
340 thread,
341 payloads,
342 })
343 }
344}
345
346impl From<Message> for Element {
347 fn from(message: Message) -> Element {
348 Element::builder("message", ns::DEFAULT_NS)
349 .attr("from", message.from)
350 .attr("to", message.to)
351 .attr("id", message.id)
352 .attr("type", message.type_)
353 .append_all(message.subjects.into_iter().map(|(lang, subject)| {
354 let mut subject = Element::from(subject);
355 subject.set_attr(
356 "xml:lang",
357 match lang.as_ref() {
358 "" => None,
359 lang => Some(lang),
360 },
361 );
362 subject
363 }))
364 .append_all(message.bodies.into_iter().map(|(lang, body)| {
365 let mut body = Element::from(body);
366 body.set_attr(
367 "xml:lang",
368 match lang.as_ref() {
369 "" => None,
370 lang => Some(lang),
371 },
372 );
373 body
374 }))
375 .append_all(message.payloads)
376 .build()
377 }
378}
379
380impl ::xso::FromXml for Message {
381 type Builder = ::xso::minidom_compat::FromEventsViaElement<Message>;
382
383 fn from_events(
384 qname: ::xso::exports::rxml::QName,
385 attrs: ::xso::exports::rxml::AttrMap,
386 ) -> Result<Self::Builder, ::xso::error::FromEventsError> {
387 if qname.0 != crate::ns::DEFAULT_NS || qname.1 != "message" {
388 return Err(::xso::error::FromEventsError::Mismatch { name: qname, attrs });
389 }
390 Self::Builder::new(qname, attrs)
391 }
392}
393
394impl ::xso::AsXml for Message {
395 type ItemIter<'x> = ::xso::minidom_compat::AsItemsViaElement<'x>;
396
397 fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, ::xso::error::Error> {
398 ::xso::minidom_compat::AsItemsViaElement::new(self.clone())
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405 use core::str::FromStr;
406
407 #[cfg(target_pointer_width = "32")]
408 #[test]
409 fn test_size() {
410 assert_size!(MessageType, 1);
411 assert_size!(Body, 12);
412 assert_size!(Subject, 12);
413 assert_size!(Thread, 12);
414 assert_size!(Message, 96);
415 }
416
417 #[cfg(target_pointer_width = "64")]
418 #[test]
419 fn test_size() {
420 assert_size!(MessageType, 1);
421 assert_size!(Body, 24);
422 assert_size!(Subject, 24);
423 assert_size!(Thread, 24);
424 assert_size!(Message, 192);
425 }
426
427 #[test]
428 fn test_simple() {
429 #[cfg(not(feature = "component"))]
430 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
431 #[cfg(feature = "component")]
432 let elem: Element = "<message xmlns='jabber:component:accept'/>"
433 .parse()
434 .unwrap();
435 let message = Message::try_from(elem).unwrap();
436 assert_eq!(message.from, None);
437 assert_eq!(message.to, None);
438 assert_eq!(message.id, None);
439 assert_eq!(message.type_, MessageType::Normal);
440 assert!(message.payloads.is_empty());
441 }
442
443 #[test]
444 fn test_serialise() {
445 #[cfg(not(feature = "component"))]
446 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
447 #[cfg(feature = "component")]
448 let elem: Element = "<message xmlns='jabber:component:accept'/>"
449 .parse()
450 .unwrap();
451 let mut message = Message::new(None);
452 message.type_ = MessageType::Normal;
453 let elem2 = message.into();
454 assert_eq!(elem, elem2);
455 }
456
457 #[test]
458 fn test_body() {
459 #[cfg(not(feature = "component"))]
460 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
461 #[cfg(feature = "component")]
462 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
463 let elem1 = elem.clone();
464 let message = Message::try_from(elem).unwrap();
465 assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap());
466
467 {
468 let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
469 assert_eq!(lang, "");
470 assert_eq!(body, &Body::from_str("Hello world!").unwrap());
471 }
472
473 let elem2 = message.into();
474 assert_eq!(elem1, elem2);
475 }
476
477 #[test]
478 fn test_serialise_body() {
479 #[cfg(not(feature = "component"))]
480 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
481 #[cfg(feature = "component")]
482 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
483 let mut message = Message::new(Jid::new("coucou@example.org").unwrap());
484 message
485 .bodies
486 .insert(String::from(""), Body::from_str("Hello world!").unwrap());
487 let elem2 = message.into();
488 assert_eq!(elem, elem2);
489 }
490
491 #[test]
492 fn test_subject() {
493 #[cfg(not(feature = "component"))]
494 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
495 #[cfg(feature = "component")]
496 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
497 let elem1 = elem.clone();
498 let message = Message::try_from(elem).unwrap();
499 assert_eq!(
500 message.subjects[""],
501 Subject::from_str("Hello world!").unwrap()
502 );
503
504 {
505 let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
506 assert_eq!(lang, "");
507 assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
508 }
509
510 {
512 let (lang, subject) = message.get_best_subject_cloned(vec!["en"]).unwrap();
513 assert_eq!(lang, "");
514 assert_eq!(subject, Subject::from_str("Hello world!").unwrap());
515 }
516
517 let elem2 = message.into();
518 assert_eq!(elem1, elem2);
519 }
520
521 #[test]
522 fn get_best_body() {
523 #[cfg(not(feature = "component"))]
524 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();
525 #[cfg(feature = "component")]
526 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
527 let message = Message::try_from(elem).unwrap();
528
529 {
531 let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
532 assert_eq!(lang, "fr");
533 assert_eq!(body, &Body::from_str("Salut le monde !").unwrap());
534 }
535
536 {
538 let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
539 assert_eq!(lang, "de");
540 assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
541 }
542
543 {
545 let (lang, body) = message.get_best_body(vec![]).unwrap();
546 assert_eq!(lang, "");
547 assert_eq!(body, &Body::from_str("Hello world!").unwrap());
548 }
549
550 {
552 let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
553 assert_eq!(lang, "");
554 assert_eq!(body, &Body::from_str("Hello world!").unwrap());
555 }
556
557 {
559 let (lang, body) = message.get_best_body_cloned(vec!["ja"]).unwrap();
560 assert_eq!(lang, "");
561 assert_eq!(body, Body::from_str("Hello world!").unwrap());
562 }
563
564 let message = Message::new(None);
565
566 assert_eq!(message.get_best_body(vec!("ja")), None);
568 }
569
570 #[test]
571 fn test_attention() {
572 #[cfg(not(feature = "component"))]
573 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
574 #[cfg(feature = "component")]
575 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
576 let elem1 = elem.clone();
577 let message = Message::try_from(elem).unwrap();
578 let elem2 = message.into();
579 assert_eq!(elem1, elem2);
580 }
581
582 #[test]
583 fn test_extract_payload() {
584 use super::super::attention::Attention;
585 use super::super::pubsub;
586
587 #[cfg(not(feature = "component"))]
588 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
589 #[cfg(feature = "component")]
590 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
591 let mut message = Message::try_from(elem).unwrap();
592 assert_eq!(message.payloads.len(), 1);
593 match message.extract_payload::<pubsub::Event>() {
594 Ok(None) => (),
595 other => panic!("unexpected result: {:?}", other),
596 };
597 assert_eq!(message.payloads.len(), 1);
598 match message.extract_payload::<Attention>() {
599 Ok(Some(_)) => (),
600 other => panic!("unexpected result: {:?}", other),
601 };
602 assert_eq!(message.payloads.len(), 0);
603 }
604}