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}