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")]
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 lang(type_ = 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 use xso::exports::rxml;
297
298 #[cfg(target_pointer_width = "32")]
299 #[test]
300 fn test_size() {
301 assert_size!(Show, 1);
302 assert_size!(Type, 1);
303 assert_size!(Presence, 72);
304 }
305
306 #[cfg(target_pointer_width = "64")]
307 #[test]
308 fn test_size() {
309 assert_size!(Show, 1);
310 assert_size!(Type, 1);
311 assert_size!(Presence, 144);
312 }
313
314 #[test]
315 fn test_simple() {
316 #[cfg(not(feature = "component"))]
317 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
318 #[cfg(feature = "component")]
319 let elem: Element = "<presence xmlns='jabber:component:accept'/>"
320 .parse()
321 .unwrap();
322 let presence = Presence::try_from(elem).unwrap();
323 assert_eq!(presence.from, None);
324 assert_eq!(presence.to, None);
325 assert_eq!(presence.id, None);
326 assert_eq!(presence.type_, Type::None);
327 assert!(presence.payloads.is_empty());
328 }
329
330 #[test]
335 #[ignore]
336 fn test_serialise() {
337 #[cfg(not(feature = "component"))]
338 let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>"
339 .parse()
340 .unwrap();
341 #[cfg(feature = "component")]
342 let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>"
343 .parse()
344 .unwrap();
345 let presence = Presence::new(Type::Unavailable);
346 let elem2 = presence.into();
347 assert_eq!(elem, elem2);
348 }
349
350 #[test]
351 fn test_show() {
352 #[cfg(not(feature = "component"))]
353 let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>"
354 .parse()
355 .unwrap();
356 #[cfg(feature = "component")]
357 let elem: Element =
358 "<presence xmlns='jabber:component:accept'><show>chat</show></presence>"
359 .parse()
360 .unwrap();
361 let presence = Presence::try_from(elem).unwrap();
362 assert_eq!(presence.payloads.len(), 0);
363 assert_eq!(presence.show, Some(Show::Chat));
364 }
365
366 #[test]
367 fn test_empty_show_value() {
368 #[cfg(not(feature = "component"))]
369 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
370 #[cfg(feature = "component")]
371 let elem: Element = "<presence xmlns='jabber:component:accept'/>"
372 .parse()
373 .unwrap();
374 let presence = Presence::try_from(elem).unwrap();
375 assert_eq!(presence.show, None);
376 }
377
378 #[test]
379 fn test_missing_show_value() {
380 #[cfg(not(feature = "component"))]
381 let elem: Element = "<presence xmlns='jabber:client'><show/></presence>"
382 .parse()
383 .unwrap();
384 #[cfg(feature = "component")]
385 let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>"
386 .parse()
387 .unwrap();
388 let error = Presence::try_from(elem).unwrap_err();
389 let message = match error {
390 FromElementError::Invalid(Error::Other(string)) => string,
391 _ => panic!(),
392 };
393 assert_eq!(message, "Invalid value for show.");
394 }
395
396 #[test]
397 fn test_invalid_show() {
398 #[cfg(not(feature = "component"))]
400 let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>"
401 .parse()
402 .unwrap();
403 #[cfg(feature = "component")]
404 let elem: Element =
405 "<presence xmlns='jabber:component:accept'><show>online</show></presence>"
406 .parse()
407 .unwrap();
408 let error = Presence::try_from(elem).unwrap_err();
409 let message = match error {
410 FromElementError::Invalid(Error::Other(string)) => string,
411 _ => panic!(),
412 };
413 assert_eq!(message, "Invalid value for show.");
414 }
415
416 #[test]
417 fn test_empty_status() {
418 #[cfg(not(feature = "component"))]
419 let elem: Element = "<presence xmlns='jabber:client'><status/></presence>"
420 .parse()
421 .unwrap();
422 #[cfg(feature = "component")]
423 let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>"
424 .parse()
425 .unwrap();
426 let presence = Presence::try_from(elem).unwrap();
427 assert_eq!(presence.payloads.len(), 0);
428 assert_eq!(presence.statuses.len(), 1);
429 assert_eq!(presence.statuses[""], "");
430 }
431
432 #[test]
433 fn test_status() {
434 #[cfg(not(feature = "component"))]
435 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>"
436 .parse()
437 .unwrap();
438 #[cfg(feature = "component")]
439 let elem: Element =
440 "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>"
441 .parse()
442 .unwrap();
443 let presence = Presence::try_from(elem).unwrap();
444 assert_eq!(presence.payloads.len(), 0);
445 assert_eq!(presence.statuses.len(), 1);
446 assert_eq!(presence.statuses[""], "Here!");
447 }
448
449 #[test]
450 fn test_multiple_statuses() {
451 #[cfg(not(feature = "component"))]
452 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
453 #[cfg(feature = "component")]
454 let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
455 let presence = Presence::try_from(elem).unwrap();
456 assert_eq!(presence.payloads.len(), 0);
457 assert_eq!(presence.statuses.len(), 2);
458 assert_eq!(presence.statuses[""], "Here!");
459 assert_eq!(presence.statuses["fr"], "Là!");
460 }
461
462 #[test]
465 #[ignore]
466 fn test_invalid_multiple_statuses() {
467 #[cfg(not(feature = "component"))]
468 let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
469 #[cfg(feature = "component")]
470 let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
471 let error = Presence::try_from(elem).unwrap_err();
472 let message = match error {
473 FromElementError::Invalid(Error::Other(string)) => string,
474 _ => panic!(),
475 };
476 assert_eq!(
477 message,
478 "Status element present twice for the same xml:lang."
479 );
480 }
481
482 #[test]
483 fn test_priority() {
484 #[cfg(not(feature = "component"))]
485 let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>"
486 .parse()
487 .unwrap();
488 #[cfg(feature = "component")]
489 let elem: Element =
490 "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>"
491 .parse()
492 .unwrap();
493 let presence = Presence::try_from(elem).unwrap();
494 assert_eq!(presence.payloads.len(), 0);
495 assert_eq!(presence.priority, Priority(-1i8));
496 }
497
498 #[test]
499 fn test_invalid_priority() {
500 #[cfg(not(feature = "component"))]
501 let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>"
502 .parse()
503 .unwrap();
504 #[cfg(feature = "component")]
505 let elem: Element =
506 "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>"
507 .parse()
508 .unwrap();
509 let error = Presence::try_from(elem).unwrap_err();
510 match error {
511 FromElementError::Invalid(Error::TextParseError(e))
512 if e.is::<core::num::ParseIntError>() =>
513 {
514 ()
515 }
516 _ => panic!(),
517 };
518 }
519
520 #[test]
521 fn test_unknown_child() {
522 #[cfg(not(feature = "component"))]
523 let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>"
524 .parse()
525 .unwrap();
526 #[cfg(feature = "component")]
527 let elem: Element =
528 "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>"
529 .parse()
530 .unwrap();
531 let presence = Presence::try_from(elem).unwrap();
532 let payload = &presence.payloads[0];
533 assert!(payload.is("test", "invalid"));
534 }
535
536 #[cfg(not(feature = "disable-validation"))]
537 #[test]
538 fn test_invalid_status_child() {
539 #[cfg(not(feature = "component"))]
540 let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>"
541 .parse()
542 .unwrap();
543 #[cfg(feature = "component")]
544 let elem: Element =
545 "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>"
546 .parse()
547 .unwrap();
548 let error = Presence::try_from(elem).unwrap_err();
549 let message = match error {
550 FromElementError::Invalid(Error::Other(string)) => string,
551 _ => panic!(),
552 };
553 assert_eq!(
554 message,
555 "Unknown child in extraction for field 'statuses' in Presence element."
556 );
557 }
558
559 #[cfg(not(feature = "disable-validation"))]
560 #[test]
561 fn test_invalid_attribute() {
562 #[cfg(not(feature = "component"))]
563 let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>"
564 .parse()
565 .unwrap();
566 #[cfg(feature = "component")]
567 let elem: Element =
568 "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>"
569 .parse()
570 .unwrap();
571 let error = Presence::try_from(elem).unwrap_err();
572 let message = match error {
573 FromElementError::Invalid(Error::Other(string)) => string,
574 _ => panic!(),
575 };
576 assert_eq!(
577 message,
578 "Unknown attribute in extraction for field 'statuses' in Presence element."
579 );
580 }
581
582 #[test]
583 fn test_serialise_status() {
584 let status = Status::from("Hello world!");
585 let mut presence = Presence::new(Type::Unavailable);
586 presence.statuses.insert(Lang::from(""), status);
587 let elem: Element = presence.into();
588 assert!(elem.is("presence", ns::DEFAULT_NS));
589 assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
590 }
591
592 #[test]
593 fn test_serialise_priority() {
594 let presence = Presence::new(Type::None).with_priority(42);
595 let elem: Element = presence.into();
596 assert!(elem.is("presence", ns::DEFAULT_NS));
597 let priority = elem.children().next().unwrap();
598 assert!(priority.is("priority", ns::DEFAULT_NS));
599 assert_eq!(priority.text(), "42");
600 }
601
602 #[test]
603 fn presence_with_to() {
604 let presence = Presence::new(Type::None);
605 let elem: Element = presence.into();
606 assert_eq!(elem.attr(rxml::xml_ncname!("to")), None);
607
608 let presence = Presence::new(Type::None).with_to(Jid::new("localhost").unwrap());
609 let elem: Element = presence.into();
610 assert_eq!(elem.attr(rxml::xml_ncname!("to")), Some("localhost"));
611
612 let presence = Presence::new(Type::None).with_to(BareJid::new("localhost").unwrap());
613 let elem: Element = presence.into();
614 assert_eq!(elem.attr(rxml::xml_ncname!("to")), Some("localhost"));
615
616 let presence =
617 Presence::new(Type::None).with_to(Jid::new("test@localhost/coucou").unwrap());
618 let elem: Element = presence.into();
619 assert_eq!(
620 elem.attr(rxml::xml_ncname!("to")),
621 Some("test@localhost/coucou")
622 );
623
624 let presence =
625 Presence::new(Type::None).with_to(FullJid::new("test@localhost/coucou").unwrap());
626 let elem: Element = presence.into();
627 assert_eq!(
628 elem.attr(rxml::xml_ncname!("to")),
629 Some("test@localhost/coucou")
630 );
631 }
632
633 #[test]
634 fn test_xml_lang() {
635 #[cfg(not(feature = "component"))]
636 let elem: Element = "<presence xmlns='jabber:client' xml:lang='fr'/>"
637 .parse()
638 .unwrap();
639 #[cfg(feature = "component")]
640 let elem: Element = "<presence xmlns='jabber:component:accept' xml:lang='fr'/>"
641 .parse()
642 .unwrap();
643 let presence = Presence::try_from(elem).unwrap();
644 assert_eq!(presence.from, None);
645 assert_eq!(presence.to, None);
646 assert_eq!(presence.id, None);
647 assert_eq!(presence.type_, Type::None);
648 assert!(presence.payloads.is_empty());
649 }
650}