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