lib.rs

  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}