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