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