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