1use xso::{error::Error, AsOptionalXmlText, AsXml, AsXmlText, FromXml, FromXmlText};
9
10use crate::message::Lang;
11use crate::ns;
12use alloc::{borrow::Cow, collections::BTreeMap};
13use jid::Jid;
14use minidom::Element;
15
16pub trait PresencePayload: TryFrom<Element> + Into<Element> {}
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum Show {
22 Away,
24
25 Chat,
27
28 Dnd,
30
31 Xa,
34}
35
36impl FromXmlText for Show {
37 fn from_xml_text(s: String) -> Result<Show, Error> {
38 Ok(match s.as_ref() {
39 "away" => Show::Away,
40 "chat" => Show::Chat,
41 "dnd" => Show::Dnd,
42 "xa" => Show::Xa,
43
44 _ => return Err(Error::Other("Invalid value for show.")),
45 })
46 }
47}
48
49impl AsXmlText for Show {
50 fn as_xml_text(&self) -> Result<Cow<'_, str>, Error> {
51 Ok(Cow::Borrowed(match self {
52 Show::Away => "away",
53 Show::Chat => "chat",
54 Show::Dnd => "dnd",
55 Show::Xa => "xa",
56 }))
57 }
58}
59
60type Status = String;
61
62#[derive(FromXml, AsXml, Debug, Default, Clone, PartialEq)]
66#[xml(namespace = ns::DEFAULT_NS, name = "priority")]
67pub struct Priority(#[xml(text)] i8);
68
69#[derive(Debug, Default, Clone, PartialEq)]
71pub enum Type {
72 #[default]
75 None,
76
77 Error,
82
83 Probe,
86
87 Subscribe,
89
90 Subscribed,
92
93 Unavailable,
95
96 Unsubscribe,
98
99 Unsubscribed,
102}
103
104impl FromXmlText for Type {
105 fn from_xml_text(s: String) -> Result<Type, Error> {
106 Ok(match s.as_ref() {
107 "error" => Type::Error,
108 "probe" => Type::Probe,
109 "subscribe" => Type::Subscribe,
110 "subscribed" => Type::Subscribed,
111 "unavailable" => Type::Unavailable,
112 "unsubscribe" => Type::Unsubscribe,
113 "unsubscribed" => Type::Unsubscribed,
114
115 _ => {
116 return Err(Error::Other(
117 "Invalid 'type' attribute on presence element.",
118 ));
119 }
120 })
121 }
122}
123
124impl AsOptionalXmlText for Type {
125 fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, Error> {
126 Ok(Some(Cow::Borrowed(match self {
127 Type::None => return Ok(None),
128
129 Type::Error => "error",
130 Type::Probe => "probe",
131 Type::Subscribe => "subscribe",
132 Type::Subscribed => "subscribed",
133 Type::Unavailable => "unavailable",
134 Type::Unsubscribe => "unsubscribe",
135 Type::Unsubscribed => "unsubscribed",
136 })))
137 }
138}
139
140#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
142#[xml(namespace = ns::DEFAULT_NS, name = "presence", discard(attribute = "xml:lang"))]
143pub struct Presence {
144 #[xml(attribute(default))]
146 pub from: Option<Jid>,
147
148 #[xml(attribute(default))]
150 pub to: Option<Jid>,
151
152 #[xml(attribute(default))]
154 pub id: Option<String>,
155
156 #[xml(attribute(default, name = "type"))]
158 pub type_: Type,
159
160 #[xml(extract(name = "show", default, fields(text(type_ = Show))))]
162 pub show: Option<Show>,
163
164 #[xml(extract(n = .., name = "status", fields(
166 attribute(type_ = Lang, name = "xml:lang", default),
167 text(type_ = String),
168 )))]
169 pub statuses: BTreeMap<Lang, Status>,
170
171 #[xml(child(default))]
174 pub priority: Priority,
175
176 #[xml(element(n = ..))]
178 pub payloads: Vec<Element>,
179}
180
181impl Presence {
182 pub fn new(type_: Type) -> Presence {
184 Presence {
185 from: None,
186 to: None,
187 id: None,
188 type_,
189 show: None,
190 statuses: BTreeMap::new(),
191 priority: Priority(0i8),
192 payloads: vec![],
193 }
194 }
195
196 pub fn available() -> Presence {
198 Self::new(Type::None)
199 }
200
201 pub fn error() -> Presence {
203 Self::new(Type::Error)
204 }
205
206 pub fn probe() -> Presence {
208 Self::new(Type::Probe)
209 }
210
211 pub fn subscribe() -> Presence {
213 Self::new(Type::Subscribe)
214 }
215
216 pub fn subscribed() -> Presence {
218 Self::new(Type::Subscribed)
219 }
220
221 pub fn unavailable() -> Presence {
223 Self::new(Type::Unavailable)
224 }
225
226 pub fn unsubscribe() -> Presence {
228 Self::new(Type::Unsubscribe)
229 }
230
231 pub fn with_from<J: Into<Jid>>(mut self, from: J) -> Presence {
235 self.from = Some(from.into());
236 self
237 }
238
239 pub fn with_to<J: Into<Jid>>(mut self, to: J) -> Presence {
242 self.to = Some(to.into());
243 self
244 }
245
246 pub fn with_id(mut self, id: String) -> Presence {
248 self.id = Some(id);
249 self
250 }
251
252 pub fn with_show(mut self, show: Show) -> Presence {
254 self.show = Some(show);
255 self
256 }
257
258 pub fn with_priority(mut self, priority: i8) -> Presence {
260 self.priority = Priority(priority);
261 self
262 }
263
264 pub fn with_payload<P: PresencePayload>(mut self, payload: P) -> Presence {
266 self.payloads.push(payload.into());
267 self
268 }
269
270 pub fn with_payloads(mut self, payloads: Vec<Element>) -> Presence {
272 self.payloads = payloads;
273 self
274 }
275
276 pub fn set_status<L, S>(&mut self, lang: L, status: S)
278 where
279 L: Into<Lang>,
280 S: Into<Status>,
281 {
282 self.statuses.insert(lang.into(), status.into());
283 }
284
285 pub fn add_payload<P: PresencePayload>(&mut self, payload: P) {
287 self.payloads.push(payload.into());
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use jid::{BareJid, FullJid};
295 use xso::error::FromElementError;
296
297 #[cfg(target_pointer_width = "32")]
298 #[test]
299 fn test_size() {
300 assert_size!(Show, 1);
301 assert_size!(Type, 1);
302 assert_size!(Presence, 72);
303 }
304
305 #[cfg(target_pointer_width = "64")]
306 #[test]
307 fn test_size() {
308 assert_size!(Show, 1);
309 assert_size!(Type, 1);
310 assert_size!(Presence, 144);
311 }
312
313 #[test]
314 fn test_simple() {
315 #[cfg(not(feature = "component"))]
316 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
317 #[cfg(feature = "component")]
318 let elem: Element = "<presence xmlns='jabber:component:accept'/>"
319 .parse()
320 .unwrap();
321 let presence = Presence::try_from(elem).unwrap();
322 assert_eq!(presence.from, None);
323 assert_eq!(presence.to, None);
324 assert_eq!(presence.id, None);
325 assert_eq!(presence.type_, Type::None);
326 assert!(presence.payloads.is_empty());
327 }
328
329 #[test]
334 #[ignore]
335 fn test_serialise() {
336 #[cfg(not(feature = "component"))]
337 let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>"
338 .parse()
339 .unwrap();
340 #[cfg(feature = "component")]
341 let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>"
342 .parse()
343 .unwrap();
344 let presence = Presence::new(Type::Unavailable);
345 let elem2 = presence.into();
346 assert_eq!(elem, elem2);
347 }
348
349 #[test]
350 fn test_show() {
351 #[cfg(not(feature = "component"))]
352 let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>"
353 .parse()
354 .unwrap();
355 #[cfg(feature = "component")]
356 let elem: Element =
357 "<presence xmlns='jabber:component:accept'><show>chat</show></presence>"
358 .parse()
359 .unwrap();
360 let presence = Presence::try_from(elem).unwrap();
361 assert_eq!(presence.payloads.len(), 0);
362 assert_eq!(presence.show, Some(Show::Chat));
363 }
364
365 #[test]
366 fn test_empty_show_value() {
367 #[cfg(not(feature = "component"))]
368 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
369 #[cfg(feature = "component")]
370 let elem: Element = "<presence xmlns='jabber:component:accept'/>"
371 .parse()
372 .unwrap();
373 let presence = Presence::try_from(elem).unwrap();
374 assert_eq!(presence.show, None);
375 }
376
377 #[test]
378 fn test_missing_show_value() {
379 #[cfg(not(feature = "component"))]
380 let elem: Element = "<presence xmlns='jabber:client'><show/></presence>"
381 .parse()
382 .unwrap();
383 #[cfg(feature = "component")]
384 let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>"
385 .parse()
386 .unwrap();
387 let error = Presence::try_from(elem).unwrap_err();
388 let message = match error {
389 FromElementError::Invalid(Error::Other(string)) => string,
390 _ => panic!(),
391 };
392 assert_eq!(message, "Invalid value for show.");
393 }
394
395 #[test]
396 fn test_invalid_show() {
397 #[cfg(not(feature = "component"))]
399 let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>"
400 .parse()
401 .unwrap();
402 #[cfg(feature = "component")]
403 let elem: Element =
404 "<presence xmlns='jabber:component:accept'><show>online</show></presence>"
405 .parse()
406 .unwrap();
407 let error = Presence::try_from(elem).unwrap_err();
408 let message = match error {
409 FromElementError::Invalid(Error::Other(string)) => string,
410 _ => panic!(),
411 };
412 assert_eq!(message, "Invalid value for show.");
413 }
414
415 #[test]
416 fn test_empty_status() {
417 #[cfg(not(feature = "component"))]
418 let elem: Element = "<presence xmlns='jabber:client'><status/></presence>"
419 .parse()
420 .unwrap();
421 #[cfg(feature = "component")]
422 let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>"
423 .parse()
424 .unwrap();
425 let presence = Presence::try_from(elem).unwrap();
426 assert_eq!(presence.payloads.len(), 0);
427 assert_eq!(presence.statuses.len(), 1);
428 assert_eq!(presence.statuses[""], "");
429 }
430
431 #[test]
432 fn test_status() {
433 #[cfg(not(feature = "component"))]
434 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>"
435 .parse()
436 .unwrap();
437 #[cfg(feature = "component")]
438 let elem: Element =
439 "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>"
440 .parse()
441 .unwrap();
442 let presence = Presence::try_from(elem).unwrap();
443 assert_eq!(presence.payloads.len(), 0);
444 assert_eq!(presence.statuses.len(), 1);
445 assert_eq!(presence.statuses[""], "Here!");
446 }
447
448 #[test]
449 fn test_multiple_statuses() {
450 #[cfg(not(feature = "component"))]
451 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
452 #[cfg(feature = "component")]
453 let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
454 let presence = Presence::try_from(elem).unwrap();
455 assert_eq!(presence.payloads.len(), 0);
456 assert_eq!(presence.statuses.len(), 2);
457 assert_eq!(presence.statuses[""], "Here!");
458 assert_eq!(presence.statuses["fr"], "Là!");
459 }
460
461 #[test]
464 #[ignore]
465 fn test_invalid_multiple_statuses() {
466 #[cfg(not(feature = "component"))]
467 let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
468 #[cfg(feature = "component")]
469 let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
470 let error = Presence::try_from(elem).unwrap_err();
471 let message = match error {
472 FromElementError::Invalid(Error::Other(string)) => string,
473 _ => panic!(),
474 };
475 assert_eq!(
476 message,
477 "Status element present twice for the same xml:lang."
478 );
479 }
480
481 #[test]
482 fn test_priority() {
483 #[cfg(not(feature = "component"))]
484 let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>"
485 .parse()
486 .unwrap();
487 #[cfg(feature = "component")]
488 let elem: Element =
489 "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>"
490 .parse()
491 .unwrap();
492 let presence = Presence::try_from(elem).unwrap();
493 assert_eq!(presence.payloads.len(), 0);
494 assert_eq!(presence.priority, Priority(-1i8));
495 }
496
497 #[test]
498 fn test_invalid_priority() {
499 #[cfg(not(feature = "component"))]
500 let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>"
501 .parse()
502 .unwrap();
503 #[cfg(feature = "component")]
504 let elem: Element =
505 "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>"
506 .parse()
507 .unwrap();
508 let error = Presence::try_from(elem).unwrap_err();
509 match error {
510 FromElementError::Invalid(Error::TextParseError(e))
511 if e.is::<core::num::ParseIntError>() =>
512 {
513 ()
514 }
515 _ => panic!(),
516 };
517 }
518
519 #[test]
520 fn test_unknown_child() {
521 #[cfg(not(feature = "component"))]
522 let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>"
523 .parse()
524 .unwrap();
525 #[cfg(feature = "component")]
526 let elem: Element =
527 "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>"
528 .parse()
529 .unwrap();
530 let presence = Presence::try_from(elem).unwrap();
531 let payload = &presence.payloads[0];
532 assert!(payload.is("test", "invalid"));
533 }
534
535 #[cfg(not(feature = "disable-validation"))]
536 #[test]
537 fn test_invalid_status_child() {
538 #[cfg(not(feature = "component"))]
539 let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>"
540 .parse()
541 .unwrap();
542 #[cfg(feature = "component")]
543 let elem: Element =
544 "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>"
545 .parse()
546 .unwrap();
547 let error = Presence::try_from(elem).unwrap_err();
548 let message = match error {
549 FromElementError::Invalid(Error::Other(string)) => string,
550 _ => panic!(),
551 };
552 assert_eq!(
553 message,
554 "Unknown child in extraction for field 'statuses' in Presence element."
555 );
556 }
557
558 #[cfg(not(feature = "disable-validation"))]
559 #[test]
560 fn test_invalid_attribute() {
561 #[cfg(not(feature = "component"))]
562 let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>"
563 .parse()
564 .unwrap();
565 #[cfg(feature = "component")]
566 let elem: Element =
567 "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>"
568 .parse()
569 .unwrap();
570 let error = Presence::try_from(elem).unwrap_err();
571 let message = match error {
572 FromElementError::Invalid(Error::Other(string)) => string,
573 _ => panic!(),
574 };
575 assert_eq!(
576 message,
577 "Unknown attribute in extraction for field 'statuses' in Presence element."
578 );
579 }
580
581 #[test]
582 fn test_serialise_status() {
583 let status = Status::from("Hello world!");
584 let mut presence = Presence::new(Type::Unavailable);
585 presence.statuses.insert(Lang::from(""), status);
586 let elem: Element = presence.into();
587 assert!(elem.is("presence", ns::DEFAULT_NS));
588 assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
589 }
590
591 #[test]
592 fn test_serialise_priority() {
593 let presence = Presence::new(Type::None).with_priority(42);
594 let elem: Element = presence.into();
595 assert!(elem.is("presence", ns::DEFAULT_NS));
596 let priority = elem.children().next().unwrap();
597 assert!(priority.is("priority", ns::DEFAULT_NS));
598 assert_eq!(priority.text(), "42");
599 }
600
601 #[test]
602 fn presence_with_to() {
603 let presence = Presence::new(Type::None);
604 let elem: Element = presence.into();
605 assert_eq!(elem.attr("to"), None);
606
607 let presence = Presence::new(Type::None).with_to(Jid::new("localhost").unwrap());
608 let elem: Element = presence.into();
609 assert_eq!(elem.attr("to"), Some("localhost"));
610
611 let presence = Presence::new(Type::None).with_to(BareJid::new("localhost").unwrap());
612 let elem: Element = presence.into();
613 assert_eq!(elem.attr("to"), Some("localhost"));
614
615 let presence =
616 Presence::new(Type::None).with_to(Jid::new("test@localhost/coucou").unwrap());
617 let elem: Element = presence.into();
618 assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
619
620 let presence =
621 Presence::new(Type::None).with_to(FullJid::new("test@localhost/coucou").unwrap());
622 let elem: Element = presence.into();
623 assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
624 }
625
626 #[test]
627 fn test_xml_lang() {
628 #[cfg(not(feature = "component"))]
629 let elem: Element = "<presence xmlns='jabber:client' xml:lang='fr'/>"
630 .parse()
631 .unwrap();
632 #[cfg(feature = "component")]
633 let elem: Element = "<presence xmlns='jabber:component:accept' xml:lang='fr'/>"
634 .parse()
635 .unwrap();
636 let presence = Presence::try_from(elem).unwrap();
637 assert_eq!(presence.from, None);
638 assert_eq!(presence.to, None);
639 assert_eq!(presence.id, None);
640 assert_eq!(presence.type_, Type::None);
641 assert!(presence.payloads.is_empty());
642 }
643}