1use crate::ns;
9use crate::stanza_error::StanzaError;
10use jid::Jid;
11use minidom::Element;
12use xso::{AsXml, FromXml};
13
14pub trait IqGetPayload: TryFrom<Element> + Into<Element> {}
16
17pub trait IqSetPayload: TryFrom<Element> + Into<Element> {}
19
20pub trait IqResultPayload: TryFrom<Element> + Into<Element> {}
22
23pub struct IqHeader {
25 pub from: Option<Jid>,
27
28 pub to: Option<Jid>,
30
31 pub id: String,
33}
34
35impl IqHeader {
36 pub fn assemble(self, data: IqPayload) -> Iq {
38 data.assemble(self)
39 }
40}
41
42pub enum IqRequestPayload {
44 Get(Element),
46
47 Set(Element),
49}
50
51pub enum IqPayload {
53 Get(Element),
55
56 Set(Element),
58
59 Result(Option<Element>),
61
62 Error(StanzaError),
64}
65
66impl IqPayload {
67 pub fn assemble(self, IqHeader { from, to, id }: IqHeader) -> Iq {
69 match self {
70 Self::Get(payload) => Iq::Get {
71 from,
72 to,
73 id,
74 payload,
75 },
76 Self::Set(payload) => Iq::Set {
77 from,
78 to,
79 id,
80 payload,
81 },
82 Self::Result(payload) => Iq::Result {
83 from,
84 to,
85 id,
86 payload,
87 },
88 Self::Error(error) => Iq::Error {
89 from,
90 to,
91 id,
92 payload: None,
93 error,
94 },
95 }
96 }
97}
98
99#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
101#[xml(namespace = ns::DEFAULT_NS, name = "iq", attribute = "type", exhaustive)]
102pub enum Iq {
103 #[xml(value = "get")]
105 Get {
106 #[xml(attribute(default))]
108 from: Option<Jid>,
109
110 #[xml(attribute(default))]
112 to: Option<Jid>,
113
114 #[xml(attribute)]
117 id: String,
118
119 #[xml(element(n = 1))]
121 payload: Element,
122 },
123
124 #[xml(value = "set")]
126 Set {
127 #[xml(attribute(default))]
129 from: Option<Jid>,
130
131 #[xml(attribute(default))]
133 to: Option<Jid>,
134
135 #[xml(attribute)]
138 id: String,
139
140 #[xml(element(n = 1))]
142 payload: Element,
143 },
144
145 #[xml(value = "result")]
147 Result {
148 #[xml(attribute(default))]
150 from: Option<Jid>,
151
152 #[xml(attribute(default))]
154 to: Option<Jid>,
155
156 #[xml(attribute)]
159 id: String,
160
161 #[xml(element(n = 1, default))]
163 payload: Option<Element>,
164 },
165
166 #[xml(value = "error")]
168 Error {
169 #[xml(attribute(default))]
171 from: Option<Jid>,
172
173 #[xml(attribute(default))]
175 to: Option<Jid>,
176
177 #[xml(attribute)]
180 id: String,
181
182 #[xml(child)]
184 error: StanzaError,
185
186 #[xml(element(n = 1, default))]
193 payload: Option<Element>,
194 },
195}
196
197impl Iq {
198 pub fn assemble(header: IqHeader, data: IqPayload) -> Self {
201 data.assemble(header)
202 }
203
204 pub fn from_get<S: Into<String>>(id: S, payload: impl IqGetPayload) -> Iq {
206 Iq::Get {
207 from: None,
208 to: None,
209 id: id.into(),
210 payload: payload.into(),
211 }
212 }
213
214 pub fn from_set<S: Into<String>>(id: S, payload: impl IqSetPayload) -> Iq {
216 Iq::Set {
217 from: None,
218 to: None,
219 id: id.into(),
220 payload: payload.into(),
221 }
222 }
223
224 pub fn empty_result<S: Into<String>>(to: Jid, id: S) -> Iq {
226 Iq::Result {
227 from: None,
228 to: Some(to),
229 id: id.into(),
230 payload: None,
231 }
232 }
233
234 pub fn from_result<S: Into<String>>(id: S, payload: Option<impl IqResultPayload>) -> Iq {
236 Iq::Result {
237 from: None,
238 to: None,
239 id: id.into(),
240 payload: payload.map(Into::into),
241 }
242 }
243
244 pub fn from_error<S: Into<String>>(id: S, payload: StanzaError) -> Iq {
246 Iq::Error {
247 from: None,
248 to: None,
249 id: id.into(),
250 error: payload,
251 payload: None,
252 }
253 }
254
255 pub fn with_to(mut self, to: Jid) -> Iq {
257 *self.to_mut() = Some(to);
258 self
259 }
260
261 pub fn with_from(mut self, from: Jid) -> Iq {
263 *self.from_mut() = Some(from);
264 self
265 }
266
267 pub fn with_id(mut self, id: String) -> Iq {
269 *self.id_mut() = id;
270 self
271 }
272
273 pub fn from(&self) -> Option<&Jid> {
275 match self {
276 Self::Get { from, .. }
277 | Self::Set { from, .. }
278 | Self::Result { from, .. }
279 | Self::Error { from, .. } => from.as_ref(),
280 }
281 }
282
283 pub fn from_mut(&mut self) -> &mut Option<Jid> {
285 match self {
286 Self::Get { ref mut from, .. }
287 | Self::Set { ref mut from, .. }
288 | Self::Result { ref mut from, .. }
289 | Self::Error { ref mut from, .. } => from,
290 }
291 }
292
293 pub fn to(&self) -> Option<&Jid> {
295 match self {
296 Self::Get { to, .. }
297 | Self::Set { to, .. }
298 | Self::Result { to, .. }
299 | Self::Error { to, .. } => to.as_ref(),
300 }
301 }
302
303 pub fn to_mut(&mut self) -> &mut Option<Jid> {
305 match self {
306 Self::Get { ref mut to, .. }
307 | Self::Set { ref mut to, .. }
308 | Self::Result { ref mut to, .. }
309 | Self::Error { ref mut to, .. } => to,
310 }
311 }
312
313 pub fn id(&self) -> &str {
315 match self {
316 Self::Get { id, .. }
317 | Self::Set { id, .. }
318 | Self::Result { id, .. }
319 | Self::Error { id, .. } => id.as_str(),
320 }
321 }
322
323 pub fn id_mut(&mut self) -> &mut String {
325 match self {
326 Self::Get { ref mut id, .. }
327 | Self::Set { ref mut id, .. }
328 | Self::Result { ref mut id, .. }
329 | Self::Error { ref mut id, .. } => id,
330 }
331 }
332
333 pub fn split(self) -> (IqHeader, IqPayload) {
339 match self {
340 Self::Get {
341 from,
342 to,
343 id,
344 payload,
345 } => (IqHeader { from, to, id }, IqPayload::Get(payload)),
346 Self::Set {
347 from,
348 to,
349 id,
350 payload,
351 } => (IqHeader { from, to, id }, IqPayload::Set(payload)),
352 Self::Result {
353 from,
354 to,
355 id,
356 payload,
357 } => (IqHeader { from, to, id }, IqPayload::Result(payload)),
358 Self::Error {
359 from,
360 to,
361 id,
362 error,
363 payload: _,
364 } => (IqHeader { from, to, id }, IqPayload::Error(error)),
365 }
366 }
367
368 pub fn into_header(self) -> IqHeader {
370 self.split().0
371 }
372
373 pub fn into_payload(self) -> IqPayload {
379 self.split().1
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use crate::disco::DiscoInfoQuery;
387 use crate::stanza_error::{DefinedCondition, ErrorType};
388 use xso::error::{Error, FromElementError};
389
390 #[cfg(target_pointer_width = "32")]
391 #[test]
392 fn test_size() {
393 assert_size!(IqHeader, 44);
394 assert_size!(IqPayload, 108);
395 assert_size!(Iq, 212);
396 }
397
398 #[cfg(target_pointer_width = "64")]
399 #[test]
400 fn test_size() {
401 assert_size!(IqHeader, 88);
402 assert_size!(IqPayload, 216);
403 assert_size!(Iq, 424);
404 }
405
406 #[test]
407 fn test_require_type() {
408 #[cfg(not(feature = "component"))]
409 let elem: Element = "<iq xmlns='jabber:client'/>".parse().unwrap();
410 #[cfg(feature = "component")]
411 let elem: Element = "<iq xmlns='jabber:component:accept'/>".parse().unwrap();
412 let error = Iq::try_from(elem).unwrap_err();
413 let message = match error {
414 FromElementError::Invalid(Error::Other(string)) => string,
415 _ => panic!(),
416 };
417 assert_eq!(message, "Missing discriminator attribute.");
418 }
419
420 #[test]
421 fn test_require_id() {
422 for type_ in ["get", "set", "result", "error"] {
423 #[cfg(not(feature = "component"))]
424 let elem: Element = format!("<iq xmlns='jabber:client' type='{}'/>", type_)
425 .parse()
426 .unwrap();
427 #[cfg(feature = "component")]
428 let elem: Element = format!("<iq xmlns='jabber:component:accept' type='{}'/>", type_)
429 .parse()
430 .unwrap();
431 let error = Iq::try_from(elem).unwrap_err();
432 let message = match error {
433 FromElementError::Invalid(Error::Other(string)) => string,
434 _ => panic!(),
435 };
436 assert_eq!(&message[..33], "Required attribute field 'id' on ");
439 }
440 }
441
442 #[test]
443 fn test_get() {
444 #[cfg(not(feature = "component"))]
445 let elem: Element = "<iq xmlns='jabber:client' type='get' id='foo'>
446 <foo xmlns='bar'/>
447 </iq>"
448 .parse()
449 .unwrap();
450 #[cfg(feature = "component")]
451 let elem: Element = "<iq xmlns='jabber:component:accept' type='get' id='foo'>
452 <foo xmlns='bar'/>
453 </iq>"
454 .parse()
455 .unwrap();
456 let iq = Iq::try_from(elem).unwrap();
457 let query: Element = "<foo xmlns='bar'/>".parse().unwrap();
458 assert_eq!(iq.from(), None);
459 assert_eq!(iq.to(), None);
460 assert_eq!(iq.id(), "foo");
461 assert!(match iq {
462 Iq::Get { payload, .. } => payload == query,
463 _ => false,
464 });
465 }
466
467 #[test]
468 fn test_set() {
469 #[cfg(not(feature = "component"))]
470 let elem: Element = "<iq xmlns='jabber:client' type='set' id='vcard'>
471 <vCard xmlns='vcard-temp'/>
472 </iq>"
473 .parse()
474 .unwrap();
475 #[cfg(feature = "component")]
476 let elem: Element = "<iq xmlns='jabber:component:accept' type='set' id='vcard'>
477 <vCard xmlns='vcard-temp'/>
478 </iq>"
479 .parse()
480 .unwrap();
481 let iq = Iq::try_from(elem).unwrap();
482 let vcard: Element = "<vCard xmlns='vcard-temp'/>".parse().unwrap();
483 assert_eq!(iq.from(), None);
484 assert_eq!(iq.to(), None);
485 assert_eq!(iq.id(), "vcard");
486 assert!(match iq {
487 Iq::Set { payload, .. } => payload == vcard,
488 _ => false,
489 });
490 }
491
492 #[test]
493 fn test_result_empty() {
494 #[cfg(not(feature = "component"))]
495 let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>"
496 .parse()
497 .unwrap();
498 #[cfg(feature = "component")]
499 let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
500 .parse()
501 .unwrap();
502 let iq = Iq::try_from(elem).unwrap();
503 assert_eq!(iq.from(), None);
504 assert_eq!(iq.to(), None);
505 assert_eq!(iq.id(), "res");
506 assert!(match iq {
507 Iq::Result { payload: None, .. } => true,
508 _ => false,
509 });
510 }
511
512 #[test]
513 fn test_result() {
514 #[cfg(not(feature = "component"))]
515 let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'>
516 <query xmlns='http://jabber.org/protocol/disco#items'/>
517 </iq>"
518 .parse()
519 .unwrap();
520 #[cfg(feature = "component")]
521 let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'>
522 <query xmlns='http://jabber.org/protocol/disco#items'/>
523 </iq>"
524 .parse()
525 .unwrap();
526 let iq = Iq::try_from(elem).unwrap();
527 let query: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>"
528 .parse()
529 .unwrap();
530 assert_eq!(iq.from(), None);
531 assert_eq!(iq.to(), None);
532 assert_eq!(iq.id(), "res");
533 assert!(match iq {
534 Iq::Result {
535 payload: Some(element),
536 ..
537 } => element == query,
538 _ => false,
539 });
540 }
541
542 #[test]
543 fn test_error() {
544 #[cfg(not(feature = "component"))]
545 let elem: Element = "<iq xmlns='jabber:client' type='error' id='err1'>
546 <ping xmlns='urn:xmpp:ping'/>
547 <error type='cancel'>
548 <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
549 </error>
550 </iq>"
551 .parse()
552 .unwrap();
553 #[cfg(feature = "component")]
554 let elem: Element = "<iq xmlns='jabber:component:accept' type='error' id='err1'>
555 <ping xmlns='urn:xmpp:ping'/>
556 <error type='cancel'>
557 <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
558 </error>
559 </iq>"
560 .parse()
561 .unwrap();
562 let iq = Iq::try_from(elem).unwrap();
563 assert_eq!(iq.from(), None);
564 assert_eq!(iq.to(), None);
565 assert_eq!(iq.id(), "err1");
566 match iq {
567 Iq::Error { error, .. } => {
568 assert_eq!(error.type_, ErrorType::Cancel);
569 assert_eq!(error.by, None);
570 assert_eq!(
571 error.defined_condition,
572 DefinedCondition::ServiceUnavailable
573 );
574 assert_eq!(error.texts.len(), 0);
575 assert_eq!(error.other, None);
576 }
577 _ => panic!(),
578 }
579 }
580
581 #[test]
582 fn test_children_invalid() {
583 #[cfg(not(feature = "component"))]
584 let elem: Element = "<iq xmlns='jabber:client' type='error' id='error'/>"
585 .parse()
586 .unwrap();
587 #[cfg(feature = "component")]
588 let elem: Element = "<iq xmlns='jabber:component:accept' type='error' id='error'/>"
589 .parse()
590 .unwrap();
591 let error = Iq::try_from(elem).unwrap_err();
592 let message = match error {
593 FromElementError::Invalid(Error::Other(string)) => string,
594 _ => panic!(),
595 };
596 assert_eq!(message, "Missing child field 'error' in Iq::Error element.");
597 }
598
599 #[test]
600 fn test_serialise() {
601 #[cfg(not(feature = "component"))]
602 let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>"
603 .parse()
604 .unwrap();
605 #[cfg(feature = "component")]
606 let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
607 .parse()
608 .unwrap();
609 let iq2 = Iq::Result {
610 from: None,
611 to: None,
612 id: String::from("res"),
613 payload: None,
614 };
615 let elem2 = iq2.into();
616 assert_eq!(elem, elem2);
617 }
618
619 #[test]
620 fn test_disco() {
621 #[cfg(not(feature = "component"))]
622 let elem: Element = "<iq xmlns='jabber:client' type='get' id='disco'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
623 #[cfg(feature = "component")]
624 let elem: Element = "<iq xmlns='jabber:component:accept' type='get' id='disco'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
625 let iq = Iq::try_from(elem).unwrap();
626 let disco_info = match iq {
627 Iq::Get { payload, .. } => DiscoInfoQuery::try_from(payload).unwrap(),
628 _ => panic!(),
629 };
630 assert!(disco_info.node.is_none());
631 }
632}