1use xso::{text::EmptyAsNone, AsXml, FromXml};
8
9use crate::message::MessagePayload;
10use crate::ns;
11use crate::presence::PresencePayload;
12use alloc::collections::BTreeMap;
13use jid::Jid;
14use minidom::Element;
15
16generate_attribute!(
17 ErrorType, "type", {
19 Auth => "auth",
21
22 Cancel => "cancel",
24
25 Continue => "continue",
27
28 Modify => "modify",
30
31 Wait => "wait",
33 }
34);
35
36#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
40#[xml(namespace = ns::XMPP_STANZAS)]
41pub enum DefinedCondition {
42 #[xml(name = "bad-request")]
49 BadRequest,
50
51 #[xml(name = "conflict")]
55 Conflict,
56
57 #[xml(name = "feature-not-implemented")]
63 FeatureNotImplemented,
64
65 #[xml(name = "forbidden")]
71 Forbidden,
72
73 #[xml(name = "gone")]
83 Gone {
84 #[xml(text(codec = EmptyAsNone))]
87 new_address: Option<String>,
88 },
89
90 #[xml(name = "internal-server-error")]
94 InternalServerError,
95
96 #[xml(name = "item-not-found")]
99 ItemNotFound,
100
101 #[xml(name = "jid-malformed")]
106 JidMalformed,
107
108 #[xml(name = "not-acceptable")]
115 NotAcceptable,
116
117 #[xml(name = "not-allowed")]
121 NotAllowed,
122
123 #[xml(name = "not-authorized")]
131 NotAuthorized,
132
133 #[xml(name = "policy-violation")]
140 PolicyViolation,
141
142 #[xml(name = "recipient-unavailable")]
145 RecipientUnavailable,
146
147 #[xml(name = "redirect")]
156 Redirect {
157 #[xml(text(codec = EmptyAsNone))]
160 new_address: Option<String>,
161 },
162
163 #[xml(name = "registration-required")]
170 RegistrationRequired,
171
172 #[xml(name = "remote-server-not-found")]
179 RemoteServerNotFound,
180
181 #[xml(name = "remote-server-timeout")]
192 RemoteServerTimeout,
193
194 #[xml(name = "resource-constraint")]
198 ResourceConstraint,
199
200 #[xml(name = "service-unavailable")]
203 ServiceUnavailable,
204
205 #[xml(name = "subscription-required")]
212 SubscriptionRequired,
213
214 #[xml(name = "undefined-condition")]
219 UndefinedCondition,
220
221 #[xml(name = "unexpected-request")]
225 UnexpectedRequest,
226}
227
228type Lang = String;
229
230#[derive(Debug, Clone, PartialEq, FromXml, AsXml)]
232#[xml(namespace = ns::DEFAULT_NS, name = "error", discard(attribute = "code"))]
233pub struct StanzaError {
234 #[xml(attribute = "type")]
236 pub type_: ErrorType,
237
238 #[xml(attribute(name = "by", default))]
240 pub by: Option<Jid>,
241
242 #[xml(child)]
244 pub defined_condition: DefinedCondition,
245
246 #[xml(extract(n = .., namespace = ns::XMPP_STANZAS, name = "text", fields(
248 lang(type_ = Lang, default),
249 text(type_ = String),
250 )))]
251 pub texts: BTreeMap<Lang, String>,
252
253 #[xml(element(default))]
255 pub other: Option<Element>,
256}
257
258impl MessagePayload for StanzaError {}
259impl PresencePayload for StanzaError {}
260
261impl StanzaError {
262 pub fn new<L, T>(
264 type_: ErrorType,
265 defined_condition: DefinedCondition,
266 lang: L,
267 text: T,
268 ) -> StanzaError
269 where
270 L: Into<Lang>,
271 T: Into<String>,
272 {
273 StanzaError {
274 type_,
275 by: None,
276 defined_condition,
277 texts: {
278 let mut map = BTreeMap::new();
279 map.insert(lang.into(), text.into());
280 map
281 },
282 other: None,
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 use xso::error::{Error, FromElementError};
292
293 #[cfg(target_pointer_width = "32")]
294 #[test]
295 fn test_size() {
296 assert_size!(ErrorType, 1);
297 assert_size!(DefinedCondition, 16);
298 assert_size!(StanzaError, 108);
299 }
300
301 #[cfg(target_pointer_width = "64")]
302 #[test]
303 fn test_size() {
304 assert_size!(ErrorType, 1);
305 assert_size!(DefinedCondition, 32);
306 assert_size!(StanzaError, 216);
307 }
308
309 #[test]
310 fn test_simple() {
311 #[cfg(not(feature = "component"))]
312 let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
313 #[cfg(feature = "component")]
314 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
315 let error = StanzaError::try_from(elem).unwrap();
316 assert_eq!(error.type_, ErrorType::Cancel);
317 assert_eq!(
318 error.defined_condition,
319 DefinedCondition::UndefinedCondition
320 );
321 }
322
323 #[test]
324 fn test_invalid_type() {
325 #[cfg(not(feature = "component"))]
326 let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
327 #[cfg(feature = "component")]
328 let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
329 let error = StanzaError::try_from(elem).unwrap_err();
330 let message = match error {
331 FromElementError::Invalid(Error::Other(string)) => string,
332 _ => panic!(),
333 };
334 assert_eq!(
335 message,
336 "Required attribute field 'type_' on StanzaError element missing."
337 );
338
339 #[cfg(not(feature = "component"))]
340 let elem: Element = "<error xmlns='jabber:client' type='coucou'/>"
341 .parse()
342 .unwrap();
343 #[cfg(feature = "component")]
344 let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>"
345 .parse()
346 .unwrap();
347 let error = StanzaError::try_from(elem).unwrap_err();
348 let message = match error {
349 FromElementError::Invalid(Error::TextParseError(string)) => string,
350 _ => panic!(),
351 };
352 assert_eq!(message.to_string(), "Unknown value for 'type' attribute.");
353 }
354
355 #[test]
356 fn test_invalid_condition() {
357 #[cfg(not(feature = "component"))]
358 let elem: Element = "<error xmlns='jabber:client' type='cancel'/>"
359 .parse()
360 .unwrap();
361 #[cfg(feature = "component")]
362 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>"
363 .parse()
364 .unwrap();
365 let error = StanzaError::try_from(elem).unwrap_err();
366 let message = match error {
367 FromElementError::Invalid(Error::Other(string)) => string,
368 _ => panic!(),
369 };
370 assert_eq!(
371 message,
372 "Missing child field 'defined_condition' in StanzaError element."
373 );
374 }
375
376 #[test]
377 fn test_error_code() {
378 #[cfg(not(feature = "component"))]
379 let elem: Element = r#"<error code="501" type="cancel" xmlns='jabber:client'>
380 <feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
381 <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>The feature requested is not implemented by the recipient or server and therefore cannot be processed.</text>
382</error>"#
383 .parse()
384 .unwrap();
385 #[cfg(feature = "component")]
386 let elem: Element = r#"<error code="501" type="cancel" xmlns='jabber:component:accept'>
387 <feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
388 <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>The feature requested is not implemented by the recipient or server and therefore cannot be processed.</text>
389</error>"#
390 .parse()
391 .unwrap();
392 let stanza_error = StanzaError::try_from(elem).unwrap();
393 assert_eq!(stanza_error.type_, ErrorType::Cancel);
394 }
395
396 #[test]
397 fn test_error_multiple_text() {
398 #[cfg(not(feature = "component"))]
399 let elem: Element = r#"<error type="cancel" xmlns='jabber:client'>
400 <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
401 <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' xml:lang="fr">Nœud non trouvé</text>
402 <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' xml:lang="en">Node not found</text>
403</error>"#
404 .parse()
405 .unwrap();
406 #[cfg(feature = "component")]
407 let elem: Element = r#"<error type="cancel" xmlns='jabber:component:accept'>
408 <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
409 <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' xml:lang="fr">Nœud non trouvé</text>
410 <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' xml:lang="en">Node not found</text>
411</error>"#
412 .parse()
413 .unwrap();
414 let stanza_error = StanzaError::try_from(elem).unwrap();
415 assert_eq!(stanza_error.type_, ErrorType::Cancel);
416 }
417
418 #[test]
419 fn test_gone_with_new_address() {
420 #[cfg(not(feature = "component"))]
421 let elem: Element = "<error xmlns='jabber:client' type='cancel'><gone xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>xmpp:room@muc.example.org?join</gone></error>"
422 .parse()
423 .unwrap();
424 #[cfg(feature = "component")]
425 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><gone xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>xmpp:room@muc.example.org?join</gone></error>"
426 .parse()
427 .unwrap();
428 let error = StanzaError::try_from(elem).unwrap();
429 assert_eq!(error.type_, ErrorType::Cancel);
430 assert_eq!(
431 error.defined_condition,
432 DefinedCondition::Gone {
433 new_address: Some("xmpp:room@muc.example.org?join".to_string()),
434 }
435 );
436 }
437
438 #[test]
439 fn test_gone_without_new_address() {
440 #[cfg(not(feature = "component"))]
441 let elem: Element = "<error xmlns='jabber:client' type='cancel'><gone xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' /></error>"
442 .parse()
443 .unwrap();
444 #[cfg(feature = "component")]
445 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><gone xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' /></error>"
446 .parse()
447 .unwrap();
448 let error = StanzaError::try_from(elem).unwrap();
449 assert_eq!(error.type_, ErrorType::Cancel);
450 assert_eq!(
451 error.defined_condition,
452 DefinedCondition::Gone { new_address: None }
453 );
454 }
455
456 #[test]
457 fn test_redirect_with_alternate_address() {
458 #[cfg(not(feature = "component"))]
459 let elem: Element = "<error xmlns='jabber:client' type='modify'><redirect xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>xmpp:characters@conference.example.org</redirect></error>"
460 .parse()
461 .unwrap();
462 #[cfg(feature = "component")]
463 let elem: Element = "<error xmlns='jabber:component:accept' type='modify'><redirect xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>xmpp:characters@conference.example.org</redirect></error>"
464 .parse()
465 .unwrap();
466 let error = StanzaError::try_from(elem).unwrap();
467 assert_eq!(error.type_, ErrorType::Modify);
468 assert_eq!(
469 error.defined_condition,
470 DefinedCondition::Redirect {
471 new_address: Some("xmpp:characters@conference.example.org".to_string()),
472 }
473 );
474 }
475
476 #[test]
477 fn test_redirect_without_alternate_address() {
478 #[cfg(not(feature = "component"))]
479 let elem: Element = "<error xmlns='jabber:client' type='modify'><redirect xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' /></error>"
480 .parse()
481 .unwrap();
482 #[cfg(feature = "component")]
483 let elem: Element = "<error xmlns='jabber:component:accept' type='modify'><redirect xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' /></error>"
484 .parse()
485 .unwrap();
486 let error = StanzaError::try_from(elem).unwrap();
487 assert_eq!(error.type_, ErrorType::Modify);
488 assert_eq!(
489 error.defined_condition,
490 DefinedCondition::Redirect { new_address: None }
491 );
492 }
493}