1// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
2// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
4// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
5// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
6//
7// This Source Code Form is subject to the terms of the Mozilla Public
8// License, v. 2.0. If a copy of the MPL was not distributed with this
9// file, You can obtain one at http://mozilla.org/MPL/2.0/.
10
11#![deny(missing_docs)]
12
13//! Provides a type for Jabber IDs.
14//!
15//! For usage, check the documentation on the `Jid` struct.
16
17use std::convert::{Into, TryFrom};
18use std::error::Error as StdError;
19use std::fmt;
20use std::str::FromStr;
21
22#[cfg(feature = "serde")]
23use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
24
25/// An error that signifies that a `Jid` cannot be parsed from a string.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum JidParseError {
28 /// Happens when there is no domain, that is either the string is empty,
29 /// starts with a /, or contains the @/ sequence.
30 NoDomain,
31
32 /// Happens when there is no resource, that is string contains no /.
33 NoResource,
34
35 /// Happens when the node is empty, that is the string starts with a @.
36 EmptyNode,
37
38 /// Happens when the resource is empty, that is the string ends with a /.
39 EmptyResource,
40}
41
42impl StdError for JidParseError {}
43
44impl fmt::Display for JidParseError {
45 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
46 write!(
47 fmt,
48 "{}",
49 match self {
50 JidParseError::NoDomain => "no domain found in this JID",
51 JidParseError::NoResource => "no resource found in this full JID",
52 JidParseError::EmptyNode => "nodepart empty despite the presence of a @",
53 JidParseError::EmptyResource => "resource empty despite the presence of a /",
54 }
55 )
56 }
57}
58
59/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub enum Jid {
63 /// Bare Jid
64 Bare(BareJid),
65
66 /// Full Jid
67 Full(FullJid),
68}
69
70impl FromStr for Jid {
71 type Err = JidParseError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 let (ns, ds, rs): StringJid = _from_str(s)?;
75 Ok(match rs {
76 Some(rs) => Jid::Full(FullJid {
77 node: ns,
78 domain: ds,
79 resource: rs,
80 }),
81 None => Jid::Bare(BareJid {
82 node: ns,
83 domain: ds,
84 }),
85 })
86 }
87}
88
89impl From<Jid> for String {
90 fn from(jid: Jid) -> String {
91 match jid {
92 Jid::Bare(bare) => String::from(bare),
93 Jid::Full(full) => String::from(full),
94 }
95 }
96}
97
98impl From<BareJid> for Jid {
99 fn from(bare_jid: BareJid) -> Jid {
100 Jid::Bare(bare_jid)
101 }
102}
103
104impl From<FullJid> for Jid {
105 fn from(full_jid: FullJid) -> Jid {
106 Jid::Full(full_jid)
107 }
108}
109
110impl fmt::Display for Jid {
111 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
112 fmt.write_str(String::from(self.clone()).as_ref())
113 }
114}
115
116impl Jid {
117 /// The node part of the Jabber ID, if it exists, else None.
118 pub fn node(self) -> Option<String> {
119 match self {
120 Jid::Bare(BareJid { node, .. }) | Jid::Full(FullJid { node, .. }) => node,
121 }
122 }
123
124 /// The domain of the Jabber ID.
125 pub fn domain(self) -> String {
126 match self {
127 Jid::Bare(BareJid { domain, .. }) | Jid::Full(FullJid { domain, .. }) => domain,
128 }
129 }
130}
131
132impl From<Jid> for BareJid {
133 fn from(jid: Jid) -> BareJid {
134 match jid {
135 Jid::Full(full) => full.into(),
136 Jid::Bare(bare) => bare,
137 }
138 }
139}
140
141impl TryFrom<Jid> for FullJid {
142 type Error = JidParseError;
143
144 fn try_from(jid: Jid) -> Result<Self, Self::Error> {
145 match jid {
146 Jid::Full(full) => Ok(full),
147 Jid::Bare(_) => Err(JidParseError::NoResource),
148 }
149 }
150}
151
152impl PartialEq<Jid> for FullJid {
153 fn eq(&self, other: &Jid) -> bool {
154 match other {
155 Jid::Full(full) => self == full,
156 Jid::Bare(_) => false,
157 }
158 }
159}
160
161impl PartialEq<Jid> for BareJid {
162 fn eq(&self, other: &Jid) -> bool {
163 match other {
164 Jid::Full(_) => false,
165 Jid::Bare(bare) => self == bare,
166 }
167 }
168}
169
170impl PartialEq<FullJid> for Jid {
171 fn eq(&self, other: &FullJid) -> bool {
172 match self {
173 Jid::Full(full) => full == other,
174 Jid::Bare(_) => false,
175 }
176 }
177}
178
179impl PartialEq<BareJid> for Jid {
180 fn eq(&self, other: &BareJid) -> bool {
181 match self {
182 Jid::Full(_) => false,
183 Jid::Bare(bare) => bare == other,
184 }
185 }
186}
187
188/// A struct representing a full Jabber ID.
189///
190/// A full Jabber ID is composed of 3 components, of which one is optional:
191///
192/// - A node/name, `node`, which is the optional part before the @.
193/// - A domain, `domain`, which is the mandatory part after the @ but before the /.
194/// - A resource, `resource`, which is the part after the /.
195///
196/// Unlike a `BareJid`, it always contains a resource, and should only be used when you are certain
197/// there is no case where a resource can be missing. Otherwise, use a `Jid` enum.
198#[derive(Clone, PartialEq, Eq, Hash)]
199pub struct FullJid {
200 /// The node part of the Jabber ID, if it exists, else None.
201 pub node: Option<String>,
202 /// The domain of the Jabber ID.
203 pub domain: String,
204 /// The resource of the Jabber ID.
205 pub resource: String,
206}
207
208/// A struct representing a bare Jabber ID.
209///
210/// A bare Jabber ID is composed of 2 components, of which one is optional:
211///
212/// - A node/name, `node`, which is the optional part before the @.
213/// - A domain, `domain`, which is the mandatory part after the @.
214///
215/// Unlike a `FullJid`, it can’t contain a resource, and should only be used when you are certain
216/// there is no case where a resource can be set. Otherwise, use a `Jid` enum.
217#[derive(Clone, PartialEq, Eq, Hash)]
218pub struct BareJid {
219 /// The node part of the Jabber ID, if it exists, else None.
220 pub node: Option<String>,
221 /// The domain of the Jabber ID.
222 pub domain: String,
223}
224
225impl From<FullJid> for String {
226 fn from(jid: FullJid) -> String {
227 String::from(&jid)
228 }
229}
230
231impl From<&FullJid> for String {
232 fn from(jid: &FullJid) -> String {
233 let mut string = String::new();
234 if let Some(ref node) = jid.node {
235 string.push_str(node);
236 string.push('@');
237 }
238 string.push_str(&jid.domain);
239 string.push('/');
240 string.push_str(&jid.resource);
241 string
242 }
243}
244
245impl From<BareJid> for String {
246 fn from(jid: BareJid) -> String {
247 String::from(&jid)
248 }
249}
250
251impl From<&BareJid> for String {
252 fn from(jid: &BareJid) -> String {
253 let mut string = String::new();
254 if let Some(ref node) = jid.node {
255 string.push_str(node);
256 string.push('@');
257 }
258 string.push_str(&jid.domain);
259 string
260 }
261}
262
263impl From<FullJid> for BareJid {
264 fn from(full: FullJid) -> BareJid {
265 BareJid {
266 node: full.node,
267 domain: full.domain,
268 }
269 }
270}
271
272impl fmt::Debug for FullJid {
273 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
274 write!(fmt, "FullJID({})", self)
275 }
276}
277
278impl fmt::Debug for BareJid {
279 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
280 write!(fmt, "BareJID({})", self)
281 }
282}
283
284impl fmt::Display for FullJid {
285 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
286 fmt.write_str(String::from(self.clone()).as_ref())
287 }
288}
289
290impl fmt::Display for BareJid {
291 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
292 fmt.write_str(String::from(self.clone()).as_ref())
293 }
294}
295
296#[cfg(feature = "serde")]
297impl Serialize for FullJid {
298 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
299 where
300 S: Serializer,
301 {
302 serializer.serialize_str(String::from(self).as_str())
303 }
304}
305
306#[cfg(feature = "serde")]
307impl Serialize for BareJid {
308 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
309 where
310 S: Serializer,
311 {
312 serializer.serialize_str(String::from(self).as_str())
313 }
314}
315
316enum ParserState {
317 Node,
318 Domain,
319 Resource,
320}
321
322type StringJid = (Option<String>, String, Option<String>);
323fn _from_str(s: &str) -> Result<StringJid, JidParseError> {
324 // TODO: very naive, may need to do it differently
325 let iter = s.chars();
326 let mut buf = String::with_capacity(s.len());
327 let mut state = ParserState::Node;
328 let mut node = None;
329 let mut domain = None;
330 let mut resource = None;
331 for c in iter {
332 match state {
333 ParserState::Node => {
334 match c {
335 '@' => {
336 if buf.is_empty() {
337 return Err(JidParseError::EmptyNode);
338 }
339 state = ParserState::Domain;
340 node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
341 buf.clear();
342 }
343 '/' => {
344 if buf.is_empty() {
345 return Err(JidParseError::NoDomain);
346 }
347 state = ParserState::Resource;
348 domain = Some(buf.clone()); // TODO: performance tweaks
349 buf.clear();
350 }
351 c => {
352 buf.push(c);
353 }
354 }
355 }
356 ParserState::Domain => {
357 match c {
358 '/' => {
359 if buf.is_empty() {
360 return Err(JidParseError::NoDomain);
361 }
362 state = ParserState::Resource;
363 domain = Some(buf.clone()); // TODO: performance tweaks
364 buf.clear();
365 }
366 c => {
367 buf.push(c);
368 }
369 }
370 }
371 ParserState::Resource => {
372 buf.push(c);
373 }
374 }
375 }
376 if !buf.is_empty() {
377 match state {
378 ParserState::Node => {
379 domain = Some(buf);
380 }
381 ParserState::Domain => {
382 domain = Some(buf);
383 }
384 ParserState::Resource => {
385 resource = Some(buf);
386 }
387 }
388 } else if let ParserState::Resource = state {
389 return Err(JidParseError::EmptyResource);
390 }
391 Ok((node, domain.ok_or(JidParseError::NoDomain)?, resource))
392}
393
394impl FromStr for FullJid {
395 type Err = JidParseError;
396
397 fn from_str(s: &str) -> Result<FullJid, JidParseError> {
398 let (ns, ds, rs): StringJid = _from_str(s)?;
399 Ok(FullJid {
400 node: ns,
401 domain: ds,
402 resource: rs.ok_or(JidParseError::NoResource)?,
403 })
404 }
405}
406
407#[cfg(feature = "serde")]
408impl<'de> Deserialize<'de> for FullJid {
409 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
410 where
411 D: Deserializer<'de>,
412 {
413 let s = String::deserialize(deserializer)?;
414 FullJid::from_str(&s).map_err(de::Error::custom)
415 }
416}
417
418#[cfg(feature = "serde")]
419impl<'de> Deserialize<'de> for BareJid {
420 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
421 where
422 D: Deserializer<'de>,
423 {
424 let s = String::deserialize(deserializer)?;
425 BareJid::from_str(&s).map_err(de::Error::custom)
426 }
427}
428
429impl FullJid {
430 /// Constructs a full Jabber ID containing all three components.
431 ///
432 /// This is of the form `node`@`domain`/`resource`.
433 ///
434 /// # Examples
435 ///
436 /// ```
437 /// use jid::FullJid;
438 ///
439 /// let jid = FullJid::new("node", "domain", "resource");
440 ///
441 /// assert_eq!(jid.node, Some("node".to_owned()));
442 /// assert_eq!(jid.domain, "domain".to_owned());
443 /// assert_eq!(jid.resource, "resource".to_owned());
444 /// ```
445 pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
446 where
447 NS: Into<String>,
448 DS: Into<String>,
449 RS: Into<String>,
450 {
451 FullJid {
452 node: Some(node.into()),
453 domain: domain.into(),
454 resource: resource.into(),
455 }
456 }
457
458 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
459 ///
460 /// # Examples
461 ///
462 /// ```
463 /// use jid::FullJid;
464 ///
465 /// let jid = FullJid::new("node", "domain", "resource");
466 ///
467 /// assert_eq!(jid.node, Some("node".to_owned()));
468 ///
469 /// let new_jid = jid.with_node("new_node");
470 ///
471 /// assert_eq!(new_jid.node, Some("new_node".to_owned()));
472 /// ```
473 pub fn with_node<NS>(&self, node: NS) -> FullJid
474 where
475 NS: Into<String>,
476 {
477 FullJid {
478 node: Some(node.into()),
479 domain: self.domain.clone(),
480 resource: self.resource.clone(),
481 }
482 }
483
484 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
485 ///
486 /// # Examples
487 ///
488 /// ```
489 /// use jid::FullJid;
490 ///
491 /// let jid = FullJid::new("node", "domain", "resource");
492 ///
493 /// assert_eq!(jid.domain, "domain".to_owned());
494 ///
495 /// let new_jid = jid.with_domain("new_domain");
496 ///
497 /// assert_eq!(new_jid.domain, "new_domain");
498 /// ```
499 pub fn with_domain<DS>(&self, domain: DS) -> FullJid
500 where
501 DS: Into<String>,
502 {
503 FullJid {
504 node: self.node.clone(),
505 domain: domain.into(),
506 resource: self.resource.clone(),
507 }
508 }
509
510 /// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
511 ///
512 /// # Examples
513 ///
514 /// ```
515 /// use jid::FullJid;
516 ///
517 /// let jid = FullJid::new("node", "domain", "resource");
518 ///
519 /// assert_eq!(jid.resource, "resource".to_owned());
520 ///
521 /// let new_jid = jid.with_resource("new_resource");
522 ///
523 /// assert_eq!(new_jid.resource, "new_resource");
524 /// ```
525 pub fn with_resource<RS>(&self, resource: RS) -> FullJid
526 where
527 RS: Into<String>,
528 {
529 FullJid {
530 node: self.node.clone(),
531 domain: self.domain.clone(),
532 resource: resource.into(),
533 }
534 }
535}
536
537impl FromStr for BareJid {
538 type Err = JidParseError;
539
540 fn from_str(s: &str) -> Result<BareJid, JidParseError> {
541 let (ns, ds, _rs): StringJid = _from_str(s)?;
542 Ok(BareJid {
543 node: ns,
544 domain: ds,
545 })
546 }
547}
548
549impl BareJid {
550 /// Constructs a bare Jabber ID, containing two components.
551 ///
552 /// This is of the form `node`@`domain`.
553 ///
554 /// # Examples
555 ///
556 /// ```
557 /// use jid::BareJid;
558 ///
559 /// let jid = BareJid::new("node", "domain");
560 ///
561 /// assert_eq!(jid.node, Some("node".to_owned()));
562 /// assert_eq!(jid.domain, "domain".to_owned());
563 /// ```
564 pub fn new<NS, DS>(node: NS, domain: DS) -> BareJid
565 where
566 NS: Into<String>,
567 DS: Into<String>,
568 {
569 BareJid {
570 node: Some(node.into()),
571 domain: domain.into(),
572 }
573 }
574
575 /// Constructs a bare Jabber ID containing only a `domain`.
576 ///
577 /// This is of the form `domain`.
578 ///
579 /// # Examples
580 ///
581 /// ```
582 /// use jid::BareJid;
583 ///
584 /// let jid = BareJid::domain("domain");
585 ///
586 /// assert_eq!(jid.node, None);
587 /// assert_eq!(jid.domain, "domain".to_owned());
588 /// ```
589 pub fn domain<DS>(domain: DS) -> BareJid
590 where
591 DS: Into<String>,
592 {
593 BareJid {
594 node: None,
595 domain: domain.into(),
596 }
597 }
598
599 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
600 ///
601 /// # Examples
602 ///
603 /// ```
604 /// use jid::BareJid;
605 ///
606 /// let jid = BareJid::domain("domain");
607 ///
608 /// assert_eq!(jid.node, None);
609 ///
610 /// let new_jid = jid.with_node("node");
611 ///
612 /// assert_eq!(new_jid.node, Some("node".to_owned()));
613 /// ```
614 pub fn with_node<NS>(&self, node: NS) -> BareJid
615 where
616 NS: Into<String>,
617 {
618 BareJid {
619 node: Some(node.into()),
620 domain: self.domain.clone(),
621 }
622 }
623
624 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
625 ///
626 /// # Examples
627 ///
628 /// ```
629 /// use jid::BareJid;
630 ///
631 /// let jid = BareJid::domain("domain");
632 ///
633 /// assert_eq!(jid.domain, "domain");
634 ///
635 /// let new_jid = jid.with_domain("new_domain");
636 ///
637 /// assert_eq!(new_jid.domain, "new_domain");
638 /// ```
639 pub fn with_domain<DS>(&self, domain: DS) -> BareJid
640 where
641 DS: Into<String>,
642 {
643 BareJid {
644 node: self.node.clone(),
645 domain: domain.into(),
646 }
647 }
648
649 /// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
650 ///
651 /// # Examples
652 ///
653 /// ```
654 /// use jid::BareJid;
655 ///
656 /// let bare = BareJid::new("node", "domain");
657 /// let full = bare.with_resource("resource");
658 ///
659 /// assert_eq!(full.node, Some("node".to_owned()));
660 /// assert_eq!(full.domain, "domain".to_owned());
661 /// assert_eq!(full.resource, "resource".to_owned());
662 /// ```
663 pub fn with_resource<RS>(self, resource: RS) -> FullJid
664 where
665 RS: Into<String>,
666 {
667 FullJid {
668 node: self.node,
669 domain: self.domain,
670 resource: resource.into(),
671 }
672 }
673}
674
675#[cfg(feature = "minidom")]
676use minidom::{IntoAttributeValue, Node};
677
678#[cfg(feature = "minidom")]
679impl IntoAttributeValue for Jid {
680 fn into_attribute_value(self) -> Option<String> {
681 Some(String::from(self))
682 }
683}
684
685#[cfg(feature = "minidom")]
686impl From<Jid> for Node {
687 fn from(jid: Jid) -> Node {
688 Node::Text(String::from(jid))
689 }
690}
691
692#[cfg(feature = "minidom")]
693impl IntoAttributeValue for FullJid {
694 fn into_attribute_value(self) -> Option<String> {
695 Some(String::from(self))
696 }
697}
698
699#[cfg(feature = "minidom")]
700impl From<FullJid> for Node {
701 fn from(jid: FullJid) -> Node {
702 Node::Text(String::from(jid))
703 }
704}
705
706#[cfg(feature = "minidom")]
707impl IntoAttributeValue for BareJid {
708 fn into_attribute_value(self) -> Option<String> {
709 Some(String::from(self))
710 }
711}
712
713#[cfg(feature = "minidom")]
714impl From<BareJid> for Node {
715 fn from(jid: BareJid) -> Node {
716 Node::Text(String::from(jid))
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 use std::collections::HashMap;
725 use std::str::FromStr;
726
727 #[test]
728 fn can_parse_full_jids() {
729 assert_eq!(
730 FullJid::from_str("a@b.c/d"),
731 Ok(FullJid::new("a", "b.c", "d"))
732 );
733 assert_eq!(
734 FullJid::from_str("b.c/d"),
735 Ok(FullJid {
736 node: None,
737 domain: "b.c".to_owned(),
738 resource: "d".to_owned(),
739 })
740 );
741
742 assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
743 assert_eq!(FullJid::from_str("b.c"), Err(JidParseError::NoResource));
744 }
745
746 #[test]
747 fn can_parse_bare_jids() {
748 assert_eq!(BareJid::from_str("a@b.c/d"), Ok(BareJid::new("a", "b.c")));
749 assert_eq!(
750 BareJid::from_str("b.c/d"),
751 Ok(BareJid {
752 node: None,
753 domain: "b.c".to_owned(),
754 })
755 );
756
757 assert_eq!(BareJid::from_str("a@b.c"), Ok(BareJid::new("a", "b.c")));
758 assert_eq!(
759 BareJid::from_str("b.c"),
760 Ok(BareJid {
761 node: None,
762 domain: "b.c".to_owned(),
763 })
764 );
765 }
766
767 #[test]
768 fn can_parse_jids() {
769 let full = FullJid::from_str("a@b.c/d").unwrap();
770 let bare = BareJid::from_str("e@f.g").unwrap();
771
772 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
773 assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
774 }
775
776 #[test]
777 fn full_to_bare_jid() {
778 let bare: BareJid = FullJid::new("a", "b.c", "d").into();
779 assert_eq!(bare, BareJid::new("a", "b.c"));
780 }
781
782 #[test]
783 fn bare_to_full_jid() {
784 assert_eq!(
785 BareJid::new("a", "b.c").with_resource("d"),
786 FullJid::new("a", "b.c", "d")
787 );
788 }
789
790 #[test]
791 fn node_from_jid() {
792 assert_eq!(
793 Jid::Full(FullJid::new("a", "b.c", "d")).node(),
794 Some(String::from("a")),
795 );
796 }
797
798 #[test]
799 fn domain_from_jid() {
800 assert_eq!(
801 Jid::Bare(BareJid::new("a", "b.c")).domain(),
802 String::from("b.c"),
803 );
804 }
805
806 #[test]
807 fn jid_to_full_bare() {
808 let full = FullJid::new("a", "b.c", "d");
809 let bare = BareJid::new("a", "b.c");
810
811 assert_eq!(FullJid::try_from(Jid::Full(full.clone())), Ok(full.clone()),);
812 assert_eq!(
813 FullJid::try_from(Jid::Bare(bare.clone())),
814 Err(JidParseError::NoResource),
815 );
816 assert_eq!(BareJid::from(Jid::Full(full.clone())), bare.clone(),);
817 assert_eq!(BareJid::from(Jid::Bare(bare.clone())), bare,);
818 }
819
820 #[test]
821 fn serialise() {
822 assert_eq!(
823 String::from(FullJid::new("a", "b", "c")),
824 String::from("a@b/c")
825 );
826 assert_eq!(String::from(BareJid::new("a", "b")), String::from("a@b"));
827 }
828
829 #[test]
830 fn hash() {
831 let _map: HashMap<Jid, String> = HashMap::new();
832 }
833
834 #[test]
835 fn invalid_jids() {
836 assert_eq!(BareJid::from_str(""), Err(JidParseError::NoDomain));
837 assert_eq!(BareJid::from_str("/c"), Err(JidParseError::NoDomain));
838 assert_eq!(BareJid::from_str("a@/c"), Err(JidParseError::NoDomain));
839 assert_eq!(BareJid::from_str("@b"), Err(JidParseError::EmptyNode));
840 assert_eq!(BareJid::from_str("b/"), Err(JidParseError::EmptyResource));
841
842 assert_eq!(FullJid::from_str(""), Err(JidParseError::NoDomain));
843 assert_eq!(FullJid::from_str("/c"), Err(JidParseError::NoDomain));
844 assert_eq!(FullJid::from_str("a@/c"), Err(JidParseError::NoDomain));
845 assert_eq!(FullJid::from_str("@b"), Err(JidParseError::EmptyNode));
846 assert_eq!(FullJid::from_str("b/"), Err(JidParseError::EmptyResource));
847 assert_eq!(FullJid::from_str("a@b"), Err(JidParseError::NoResource));
848 }
849
850 #[test]
851 fn display_jids() {
852 assert_eq!(
853 format!("{}", FullJid::new("a", "b", "c")),
854 String::from("a@b/c")
855 );
856 assert_eq!(format!("{}", BareJid::new("a", "b")), String::from("a@b"));
857 assert_eq!(
858 format!("{}", Jid::Full(FullJid::new("a", "b", "c"))),
859 String::from("a@b/c")
860 );
861 assert_eq!(
862 format!("{}", Jid::Bare(BareJid::new("a", "b"))),
863 String::from("a@b")
864 );
865 }
866
867 #[cfg(feature = "minidom")]
868 #[test]
869 fn minidom() {
870 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
871 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
872 assert_eq!(to, Jid::Full(FullJid::new("a", "b", "c")));
873
874 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
875 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
876 assert_eq!(to, Jid::Bare(BareJid::new("a", "b")));
877
878 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
879 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
880 assert_eq!(to, FullJid::new("a", "b", "c"));
881
882 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
883 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
884 assert_eq!(to, BareJid::new("a", "b"));
885 }
886
887 #[cfg(feature = "minidom")]
888 #[test]
889 fn minidom_into_attr() {
890 let full = FullJid::new("a", "b", "c");
891 let elem = minidom::Element::builder("message", "jabber:client")
892 .attr("from", full.clone())
893 .build();
894 assert_eq!(elem.attr("from"), Some(String::from(full).as_ref()));
895
896 let bare = BareJid::new("a", "b");
897 let elem = minidom::Element::builder("message", "jabber:client")
898 .attr("from", bare.clone())
899 .build();
900 assert_eq!(elem.attr("from"), Some(String::from(bare.clone()).as_ref()));
901
902 let jid = Jid::Bare(bare.clone());
903 let _elem = minidom::Element::builder("message", "jabber:client")
904 .attr("from", jid)
905 .build();
906 assert_eq!(elem.attr("from"), Some(String::from(bare).as_ref()));
907 }
908}