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