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