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, DomainRef, NodePart, NodeRef, ResourcePart, ResourceRef};
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, PartialOrd, Ord)]
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().map(|x| x.as_str()), Some("node"));
110 /// assert_eq!(jid.domain().as_str(), "domain");
111 /// assert_eq!(jid.resource().map(|x| x.as_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 ///
134 /// This method allocates and does not consume the typed parts. To avoid
135 /// allocation if both `node` and `resource` are known to be `None` and
136 /// `domain` is owned, you can use `domain.into()`.
137 pub fn from_parts(
138 node: Option<&NodeRef>,
139 domain: &DomainRef,
140 resource: Option<&ResourceRef>,
141 ) -> Jid {
142 if let Some(resource) = resource {
143 Jid::Full(FullJid::from_parts(node, domain, resource))
144 } else {
145 Jid::Bare(BareJid::from_parts(node, domain))
146 }
147 }
148
149 /// The optional node part of the JID as reference.
150 pub fn node(&self) -> Option<&NodeRef> {
151 match self {
152 Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.node(),
153 }
154 }
155
156 /// The domain part of the JID as reference
157 pub fn domain(&self) -> &DomainRef {
158 match self {
159 Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.domain(),
160 }
161 }
162
163 /// The optional resource of the Jabber ID. It is guaranteed to be present when the JID is
164 /// a Full variant, which you can check with [`Jid::is_full`].
165 pub fn resource(&self) -> Option<&ResourceRef> {
166 match self {
167 Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.resource(),
168 }
169 }
170
171 /// Allocate a new [`BareJid`] from this JID, discarding the resource.
172 pub fn to_bare(&self) -> BareJid {
173 match self {
174 Jid::Full(jid) => jid.to_bare(),
175 Jid::Bare(jid) => jid.clone(),
176 }
177 }
178
179 /// Transforms this JID into a [`BareJid`], throwing away the resource.
180 pub fn into_bare(self) -> BareJid {
181 match self {
182 Jid::Full(jid) => jid.into_bare(),
183 Jid::Bare(jid) => jid,
184 }
185 }
186
187 /// Checks if the JID contains a [`FullJid`]
188 pub fn is_full(&self) -> bool {
189 match self {
190 Self::Full(_) => true,
191 Self::Bare(_) => false,
192 }
193 }
194
195 /// Checks if the JID contains a [`BareJid`]
196 pub fn is_bare(&self) -> bool {
197 !self.is_full()
198 }
199
200 /// Return a reference to the canonical string representation of the JID.
201 pub fn as_str(&self) -> &str {
202 match self {
203 Jid::Bare(BareJid { inner }) | Jid::Full(FullJid { inner }) => inner.as_str(),
204 }
205 }
206}
207
208impl TryFrom<Jid> for FullJid {
209 type Error = Error;
210
211 fn try_from(jid: Jid) -> Result<Self, Self::Error> {
212 match jid {
213 Jid::Full(full) => Ok(full),
214 Jid::Bare(_) => Err(Error::ResourceMissingInFullJid),
215 }
216 }
217}
218
219impl PartialEq<Jid> for FullJid {
220 fn eq(&self, other: &Jid) -> bool {
221 match other {
222 Jid::Full(full) => self == full,
223 Jid::Bare(_) => false,
224 }
225 }
226}
227
228impl PartialEq<Jid> for BareJid {
229 fn eq(&self, other: &Jid) -> bool {
230 match other {
231 Jid::Full(_) => false,
232 Jid::Bare(bare) => self == bare,
233 }
234 }
235}
236
237impl PartialEq<FullJid> for Jid {
238 fn eq(&self, other: &FullJid) -> bool {
239 match self {
240 Jid::Full(full) => full == other,
241 Jid::Bare(_) => false,
242 }
243 }
244}
245
246impl PartialEq<BareJid> for Jid {
247 fn eq(&self, other: &BareJid) -> bool {
248 match self {
249 Jid::Full(_) => false,
250 Jid::Bare(bare) => bare == other,
251 }
252 }
253}
254
255/// A struct representing a full Jabber ID, with a resource part.
256///
257/// A full JID is composed of 3 components, of which only the node is optional:
258///
259/// - the (optional) node part is the part before the (optional) `@`.
260/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
261/// - the resource part after the `/`.
262///
263/// Unlike a [`BareJid`], it always contains a resource, and should only be used when you are
264/// certain there is no case where a resource can be missing. Otherwise, use a [`Jid`] or
265/// [`BareJid`].
266#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
267pub struct FullJid {
268 inner: InnerJid,
269}
270
271/// A struct representing a bare Jabber ID, without a resource part.
272///
273/// A bare JID is composed of 2 components, of which only the node is optional:
274/// - the (optional) node part is the part before the (optional) `@`.
275/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
276///
277/// Unlike a [`FullJid`], it can’t contain a resource, and should only be used when you are certain
278/// there is no case where a resource can be set. Otherwise, use a [`Jid`] or [`FullJid`].
279#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
280pub struct BareJid {
281 inner: InnerJid,
282}
283
284impl fmt::Debug for FullJid {
285 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
286 fmt.debug_tuple("FullJid").field(&self.inner).finish()
287 }
288}
289
290impl fmt::Debug for BareJid {
291 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
292 fmt.debug_tuple("BareJid").field(&self.inner).finish()
293 }
294}
295
296impl fmt::Display for FullJid {
297 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
298 fmt.write_str(&self.inner.normalized)
299 }
300}
301
302impl fmt::Display for BareJid {
303 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
304 fmt.write_str(&self.inner.normalized)
305 }
306}
307
308#[cfg(feature = "serde")]
309impl Serialize for FullJid {
310 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
311 where
312 S: Serializer,
313 {
314 serializer.serialize_str(&self.inner.normalized)
315 }
316}
317
318#[cfg(feature = "serde")]
319impl Serialize for BareJid {
320 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
321 where
322 S: Serializer,
323 {
324 serializer.serialize_str(&self.inner.normalized)
325 }
326}
327
328impl FromStr for FullJid {
329 type Err = Error;
330
331 fn from_str(s: &str) -> Result<FullJid, Error> {
332 FullJid::new(s)
333 }
334}
335
336#[cfg(feature = "serde")]
337impl<'de> Deserialize<'de> for FullJid {
338 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
339 where
340 D: Deserializer<'de>,
341 {
342 let s = String::deserialize(deserializer)?;
343 FullJid::from_str(&s).map_err(de::Error::custom)
344 }
345}
346
347#[cfg(feature = "serde")]
348impl<'de> Deserialize<'de> for BareJid {
349 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
350 where
351 D: Deserializer<'de>,
352 {
353 let s = String::deserialize(deserializer)?;
354 BareJid::from_str(&s).map_err(de::Error::custom)
355 }
356}
357
358#[cfg(feature = "quote")]
359impl ToTokens for Jid {
360 fn to_tokens(&self, tokens: &mut TokenStream) {
361 tokens.extend(match self {
362 Jid::Full(full) => quote! { Jid::Full(#full) },
363 Jid::Bare(bare) => quote! { Jid::Bare(#bare) },
364 });
365 }
366}
367
368#[cfg(feature = "quote")]
369impl ToTokens for FullJid {
370 fn to_tokens(&self, tokens: &mut TokenStream) {
371 let inner = &self.inner.normalized;
372 let t = quote! { FullJid::new(#inner).unwrap() };
373 tokens.extend(t);
374 }
375}
376
377#[cfg(feature = "quote")]
378impl ToTokens for BareJid {
379 fn to_tokens(&self, tokens: &mut TokenStream) {
380 let inner = &self.inner.normalized;
381 let t = quote! { BareJid::new(#inner).unwrap() };
382 tokens.extend(t);
383 }
384}
385
386impl FullJid {
387 /// Constructs a full Jabber ID containing all three components. This is of the form
388 /// `node@domain/resource`, where node part is optional.
389 /// If you want a non-fallible version, use [`FullJid::from_parts`] instead.
390 ///
391 /// # Examples
392 ///
393 /// ```
394 /// use jid::FullJid;
395 /// # use jid::Error;
396 ///
397 /// # fn main() -> Result<(), Error> {
398 /// let jid = FullJid::new("node@domain/resource")?;
399 ///
400 /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
401 /// assert_eq!(jid.domain().as_str(), "domain");
402 /// assert_eq!(jid.resource().as_str(), "resource");
403 /// # Ok(())
404 /// # }
405 /// ```
406 pub fn new(s: &str) -> Result<FullJid, Error> {
407 let inner = InnerJid::new(s)?;
408 if inner.slash.is_some() {
409 Ok(FullJid { inner })
410 } else {
411 Err(Error::ResourceMissingInFullJid)
412 }
413 }
414
415 /// Returns the inner String of this JID.
416 pub fn into_inner(self) -> String {
417 self.inner.normalized
418 }
419
420 /// Build a [`FullJid`] from typed parts. This method cannot fail because it uses parts that have
421 /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
422 /// This method allocates and does not consume the typed parts.
423 pub fn from_parts(
424 node: Option<&NodeRef>,
425 domain: &DomainRef,
426 resource: &ResourceRef,
427 ) -> FullJid {
428 let (at, slash, normalized) = if let Some(node) = node {
429 // Parts are never empty so len > 0 for NonZeroU16::new is always Some
430 (
431 NonZeroU16::new(node.len() as u16),
432 NonZeroU16::new((node.len() + 1 + domain.len()) as u16),
433 format!(
434 "{}@{}/{}",
435 node.as_str(),
436 domain.as_str(),
437 resource.as_str()
438 ),
439 )
440 } else {
441 (
442 None,
443 NonZeroU16::new(domain.len() as u16),
444 format!("{}/{}", domain.as_str(), resource.as_str()),
445 )
446 };
447
448 let inner = InnerJid {
449 normalized,
450 at,
451 slash,
452 };
453
454 FullJid { inner }
455 }
456
457 /// The optional node part of the JID as reference.
458 pub fn node(&self) -> Option<&NodeRef> {
459 self.inner.node()
460 }
461
462 /// The domain part of the JID as reference
463 pub fn domain(&self) -> &DomainRef {
464 self.inner.domain()
465 }
466
467 /// The optional resource of the Jabber ID. Since this is a full JID it is always present.
468 pub fn resource(&self) -> &ResourceRef {
469 self.inner.resource().unwrap()
470 }
471
472 /// Allocate a new [`BareJid`] from this full JID, discarding the resource.
473 pub fn to_bare(&self) -> BareJid {
474 let slash = self.inner.slash.unwrap().get() as usize;
475 let normalized = self.inner.normalized[..slash].to_string();
476 let inner = InnerJid {
477 normalized,
478 at: self.inner.at,
479 slash: None,
480 };
481 BareJid { inner }
482 }
483
484 /// Transforms this full JID into a [`BareJid`], discarding the resource.
485 pub fn into_bare(mut self) -> BareJid {
486 let slash = self.inner.slash.unwrap().get() as usize;
487 self.inner.normalized.truncate(slash);
488 self.inner.normalized.shrink_to_fit();
489 self.inner.slash = None;
490 BareJid { inner: self.inner }
491 }
492
493 /// Return a reference to the canonical string representation of the JID.
494 pub fn as_str(&self) -> &str {
495 self.inner.as_str()
496 }
497}
498
499impl FromStr for BareJid {
500 type Err = Error;
501
502 fn from_str(s: &str) -> Result<BareJid, Error> {
503 BareJid::new(s)
504 }
505}
506
507impl BareJid {
508 /// Constructs a bare Jabber ID, containing two components. This is of the form
509 /// `node`@`domain`, where node part is optional.
510 /// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
511 ///
512 /// # Examples
513 ///
514 /// ```
515 /// use jid::BareJid;
516 /// # use jid::Error;
517 ///
518 /// # fn main() -> Result<(), Error> {
519 /// let jid = BareJid::new("node@domain")?;
520 ///
521 /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
522 /// assert_eq!(jid.domain().as_str(), "domain");
523 /// # Ok(())
524 /// # }
525 /// ```
526 pub fn new(s: &str) -> Result<BareJid, Error> {
527 let inner = InnerJid::new(s)?;
528 if inner.slash.is_none() {
529 Ok(BareJid { inner })
530 } else {
531 Err(Error::ResourceInBareJid)
532 }
533 }
534
535 /// Returns the inner String of this JID.
536 pub fn into_inner(self) -> String {
537 self.inner.normalized
538 }
539
540 /// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
541 /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
542 ///
543 /// This method allocates and does not consume the typed parts. To avoid
544 /// allocation if `node` is known to be `None` and `domain` is owned, you
545 /// can use `domain.into()`.
546 pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> BareJid {
547 let (at, normalized) = if let Some(node) = node {
548 // Parts are never empty so len > 0 for NonZeroU16::new is always Some
549 (
550 NonZeroU16::new(node.len() as u16),
551 format!("{}@{}", node.as_str(), domain.as_str()),
552 )
553 } else {
554 (None, domain.to_string())
555 };
556
557 let inner = InnerJid {
558 normalized,
559 at,
560 slash: None,
561 };
562
563 BareJid { inner }
564 }
565
566 /// The optional node part of the JID as reference.
567 pub fn node(&self) -> Option<&NodeRef> {
568 self.inner.node()
569 }
570
571 /// The domain part of the JID as reference
572 pub fn domain(&self) -> &DomainRef {
573 self.inner.domain()
574 }
575
576 /// Constructs a [`BareJid`] from the bare JID, by specifying a [`ResourcePart`].
577 /// If you'd like to specify a stringy resource, use [`BareJid::with_resource_str`] instead.
578 ///
579 /// # Examples
580 ///
581 /// ```
582 /// use jid::{BareJid, ResourcePart};
583 ///
584 /// let resource = ResourcePart::new("resource").unwrap();
585 /// let bare = BareJid::new("node@domain").unwrap();
586 /// let full = bare.with_resource(&resource);
587 ///
588 /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
589 /// assert_eq!(full.domain().as_str(), "domain");
590 /// assert_eq!(full.resource().as_str(), "resource");
591 /// ```
592 pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
593 let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
594 let normalized = format!("{}/{resource}", self.inner.normalized);
595 let inner = InnerJid {
596 normalized,
597 at: self.inner.at,
598 slash,
599 };
600
601 FullJid { inner }
602 }
603
604 /// Constructs a [`FullJid`] from the bare JID, by specifying a stringy `resource`.
605 /// If your resource has already been parsed into a [`ResourcePart`], use [`BareJid::with_resource`].
606 ///
607 /// # Examples
608 ///
609 /// ```
610 /// use jid::BareJid;
611 ///
612 /// let bare = BareJid::new("node@domain").unwrap();
613 /// let full = bare.with_resource_str("resource").unwrap();
614 ///
615 /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
616 /// assert_eq!(full.domain().as_str(), "domain");
617 /// assert_eq!(full.resource().as_str(), "resource");
618 /// ```
619 pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
620 let resource = ResourcePart::new(resource)?;
621 Ok(self.with_resource(&resource))
622 }
623
624 /// Return a reference to the canonical string representation of the JID.
625 pub fn as_str(&self) -> &str {
626 self.inner.as_str()
627 }
628}
629
630#[cfg(feature = "minidom")]
631use minidom::{IntoAttributeValue, Node};
632
633#[cfg(feature = "minidom")]
634impl IntoAttributeValue for Jid {
635 fn into_attribute_value(self) -> Option<String> {
636 Some(self.to_string())
637 }
638}
639
640#[cfg(feature = "minidom")]
641impl From<Jid> for Node {
642 fn from(jid: Jid) -> Node {
643 Node::Text(jid.to_string())
644 }
645}
646
647#[cfg(feature = "minidom")]
648impl IntoAttributeValue for FullJid {
649 fn into_attribute_value(self) -> Option<String> {
650 Some(self.to_string())
651 }
652}
653
654#[cfg(feature = "minidom")]
655impl From<FullJid> for Node {
656 fn from(jid: FullJid) -> Node {
657 Node::Text(jid.to_string())
658 }
659}
660
661#[cfg(feature = "minidom")]
662impl IntoAttributeValue for BareJid {
663 fn into_attribute_value(self) -> Option<String> {
664 Some(self.to_string())
665 }
666}
667
668#[cfg(feature = "minidom")]
669impl From<BareJid> for Node {
670 fn from(jid: BareJid) -> Node {
671 Node::Text(jid.to_string())
672 }
673}
674
675#[cfg(test)]
676mod tests {
677 use super::*;
678
679 use std::collections::HashMap;
680
681 macro_rules! assert_size (
682 ($t:ty, $sz:expr) => (
683 assert_eq!(::std::mem::size_of::<$t>(), $sz);
684 );
685 );
686
687 #[cfg(target_pointer_width = "32")]
688 #[test]
689 fn test_size() {
690 assert_size!(BareJid, 16);
691 assert_size!(FullJid, 16);
692 assert_size!(Jid, 20);
693 }
694
695 #[cfg(target_pointer_width = "64")]
696 #[test]
697 fn test_size() {
698 assert_size!(BareJid, 32);
699 assert_size!(FullJid, 32);
700 assert_size!(Jid, 40);
701 }
702
703 #[test]
704 fn can_parse_full_jids() {
705 assert_eq!(
706 FullJid::from_str("a@b.c/d"),
707 Ok(FullJid::new("a@b.c/d").unwrap())
708 );
709 assert_eq!(
710 FullJid::from_str("b.c/d"),
711 Ok(FullJid::new("b.c/d").unwrap())
712 );
713
714 assert_eq!(
715 FullJid::from_str("a@b.c"),
716 Err(Error::ResourceMissingInFullJid)
717 );
718 assert_eq!(
719 FullJid::from_str("b.c"),
720 Err(Error::ResourceMissingInFullJid)
721 );
722 }
723
724 #[test]
725 fn can_parse_bare_jids() {
726 assert_eq!(
727 BareJid::from_str("a@b.c"),
728 Ok(BareJid::new("a@b.c").unwrap())
729 );
730 assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
731 }
732
733 #[test]
734 fn can_parse_jids() {
735 let full = FullJid::from_str("a@b.c/d").unwrap();
736 let bare = BareJid::from_str("e@f.g").unwrap();
737
738 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
739 assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
740 }
741
742 #[test]
743 fn full_to_bare_jid() {
744 let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
745 assert_eq!(bare, BareJid::new("a@b.c").unwrap());
746 }
747
748 #[test]
749 fn bare_to_full_jid_str() {
750 assert_eq!(
751 BareJid::new("a@b.c")
752 .unwrap()
753 .with_resource_str("d")
754 .unwrap(),
755 FullJid::new("a@b.c/d").unwrap()
756 );
757 }
758
759 #[test]
760 fn bare_to_full_jid() {
761 assert_eq!(
762 BareJid::new("a@b.c")
763 .unwrap()
764 .with_resource(&ResourcePart::new("d").unwrap()),
765 FullJid::new("a@b.c/d").unwrap()
766 )
767 }
768
769 #[test]
770 fn node_from_jid() {
771 let jid = Jid::new("a@b.c/d").unwrap();
772
773 assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
774 }
775
776 #[test]
777 fn domain_from_jid() {
778 let jid = Jid::new("a@b.c").unwrap();
779
780 assert_eq!(jid.domain().as_str(), "b.c");
781 }
782
783 #[test]
784 fn resource_from_jid() {
785 let jid = Jid::new("a@b.c/d").unwrap();
786
787 assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
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
931 #[test]
932 fn jid_into_parts_and_from_parts() {
933 let node = NodePart::new("node").unwrap();
934 let domain = DomainPart::new("domain").unwrap();
935
936 let jid1 = domain.with_node(&node);
937 let jid2 = node.with_domain(&domain);
938 let jid3 = BareJid::new("node@domain").unwrap();
939 assert_eq!(jid1, jid2);
940 assert_eq!(jid2, jid3);
941 }
942}