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