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