1#![no_std]
12#![deny(missing_docs)]
13#![cfg_attr(docsrs, feature(doc_auto_cfg))]
14
15extern crate alloc;
36
37#[cfg(test)]
38extern crate std;
39
40use alloc::borrow::Cow;
41use alloc::format;
42use alloc::string::{String, ToString};
43use core::borrow::Borrow;
44use core::cmp::Ordering;
45use core::fmt;
46use core::hash::{Hash, Hasher};
47use core::mem;
48use core::net::{Ipv4Addr, Ipv6Addr};
49use core::num::NonZeroU16;
50use core::ops::Deref;
51use core::str::FromStr;
52
53use memchr::memchr2_iter;
54
55use idna::uts46::{AsciiDenyList, DnsLength, Hyphens, Uts46};
56use stringprep::{nameprep, nodeprep, resourceprep};
57
58#[cfg(feature = "serde")]
59use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
60
61#[cfg(feature = "quote")]
62use proc_macro2::TokenStream;
63#[cfg(feature = "quote")]
64use quote::{quote, ToTokens};
65
66#[cfg(feature = "minidom")]
67use minidom::{IntoAttributeValue, Node};
68
69mod error;
70mod parts;
71
72pub use crate::error::Error;
73pub use parts::{DomainPart, DomainRef, NodePart, NodeRef, ResourcePart, ResourceRef};
74
75fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
76 if len == 0 {
77 Err(error_empty)
78 } else if len > 1023 {
79 Err(error_too_long)
80 } else {
81 Ok(())
82 }
83}
84
85fn node_check(node: &str) -> Result<Cow<'_, str>, Error> {
86 let node = nodeprep(node).map_err(|_| Error::NodePrep)?;
87 length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
88 Ok(node)
89}
90
91fn resource_check(resource: &str) -> Result<Cow<'_, str>, Error> {
92 let resource = resourceprep(resource).map_err(|_| Error::ResourcePrep)?;
93 length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
94 Ok(resource)
95}
96
97fn domain_check(mut domain: &str) -> Result<Cow<'_, str>, Error> {
98 if Ipv4Addr::from_str(domain).is_ok() {
100 return Ok(Cow::Borrowed(domain));
101 }
102
103 if domain.starts_with('[') && domain.ends_with(']') {
105 if Ipv6Addr::from_str(&domain[1..domain.len() - 1]).is_ok() {
106 return Ok(Cow::Borrowed(domain));
107 }
108 }
109
110 if domain.ends_with('.') {
113 domain = &domain[..domain.len() - 1];
114 }
115
116 Uts46::new()
117 .to_ascii(
118 domain.as_bytes(),
119 AsciiDenyList::URL,
120 Hyphens::Check,
121 DnsLength::Verify,
122 )
123 .map_err(|_| Error::Idna)?;
124 let domain = nameprep(domain).map_err(|_| Error::NamePrep)?;
125 Ok(domain)
126}
127
128#[derive(Clone, Eq)]
140pub struct Jid {
141 normalized: String,
142 at: Option<NonZeroU16>,
143 slash: Option<NonZeroU16>,
144}
145
146impl PartialEq for Jid {
147 fn eq(&self, other: &Jid) -> bool {
148 self.normalized == other.normalized
149 }
150}
151
152impl PartialOrd for Jid {
153 fn partial_cmp(&self, other: &Jid) -> Option<Ordering> {
154 Some(self.cmp(other))
155 }
156}
157
158impl Ord for Jid {
159 fn cmp(&self, other: &Jid) -> Ordering {
160 self.normalized.cmp(&other.normalized)
161 }
162}
163
164impl Hash for Jid {
165 fn hash<H: Hasher>(&self, state: &mut H) {
166 self.normalized.hash(state)
167 }
168}
169
170impl FromStr for Jid {
171 type Err = Error;
172
173 fn from_str(s: &str) -> Result<Self, Self::Err> {
174 Self::new(s)
175 }
176}
177
178impl From<BareJid> for Jid {
179 fn from(other: BareJid) -> Self {
180 other.inner
181 }
182}
183
184impl From<FullJid> for Jid {
185 fn from(other: FullJid) -> Self {
186 other.inner
187 }
188}
189
190impl fmt::Display for Jid {
191 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
192 fmt.write_str(&self.normalized)
193 }
194}
195
196impl Jid {
197 pub fn new(unnormalized: &str) -> Result<Jid, Error> {
217 let bytes = unnormalized.as_bytes();
218 let orig_at;
219 let orig_slash;
220 let mut iter = memchr2_iter(b'@', b'/', bytes);
221 let normalized = if let Some(first_index) = iter.next() {
222 let byte = bytes[first_index];
223 if byte == b'@' {
224 if let Some(second_index) = iter.next() {
225 let byte = bytes[second_index];
226 if byte == b'/' {
227 let node = node_check(&unnormalized[..first_index])?;
228 let domain = domain_check(&unnormalized[first_index + 1..second_index])?;
229 let resource = resource_check(&unnormalized[second_index + 1..])?;
230
231 orig_at = Some(node.len());
232 orig_slash = Some(node.len() + domain.len() + 1);
233 match (node, domain, resource) {
234 (Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
235 unnormalized.to_string()
236 }
237 (node, domain, resource) => format!("{node}@{domain}/{resource}"),
238 }
239 } else
240 {
242 return Err(Error::TooManyAts);
243 }
244 } else {
245 let node = node_check(&unnormalized[..first_index])?;
248 let domain = domain_check(&unnormalized[first_index + 1..])?;
249
250 orig_at = Some(node.len());
251 orig_slash = None;
252 match (node, domain) {
253 (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
254 (node, domain) => format!("{node}@{domain}"),
255 }
256 }
257 } else
258 {
260 let domain = domain_check(&unnormalized[..first_index])?;
264 let resource = resource_check(&unnormalized[first_index + 1..])?;
265
266 orig_at = None;
267 orig_slash = Some(domain.len());
268 match (domain, resource) {
269 (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
270 (domain, resource) => format!("{domain}/{resource}"),
271 }
272 }
273 } else {
274 let domain = domain_check(unnormalized)?;
276
277 orig_at = None;
278 orig_slash = None;
279 domain.into_owned()
280 };
281
282 Ok(Self {
283 normalized,
284 at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
285 slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
286 })
287 }
288
289 pub fn into_inner(self) -> String {
291 self.normalized
292 }
293
294 pub fn from_parts(
301 node: Option<&NodeRef>,
302 domain: &DomainRef,
303 resource: Option<&ResourceRef>,
304 ) -> Self {
305 match resource {
306 Some(resource) => FullJid::from_parts(node, domain, resource).into(),
307 None => BareJid::from_parts(node, domain).into(),
308 }
309 }
310
311 pub fn node(&self) -> Option<&NodeRef> {
313 self.at.map(|at| {
314 let at = u16::from(at) as usize;
315 NodeRef::from_str_unchecked(&self.normalized[..at])
316 })
317 }
318
319 pub fn domain(&self) -> &DomainRef {
321 match (self.at, self.slash) {
322 (Some(at), Some(slash)) => {
323 let at = u16::from(at) as usize;
324 let slash = u16::from(slash) as usize;
325 DomainRef::from_str_unchecked(&self.normalized[at + 1..slash])
326 }
327 (Some(at), None) => {
328 let at = u16::from(at) as usize;
329 DomainRef::from_str_unchecked(&self.normalized[at + 1..])
330 }
331 (None, Some(slash)) => {
332 let slash = u16::from(slash) as usize;
333 DomainRef::from_str_unchecked(&self.normalized[..slash])
334 }
335 (None, None) => DomainRef::from_str_unchecked(&self.normalized),
336 }
337 }
338
339 pub fn resource(&self) -> Option<&ResourceRef> {
342 self.slash.map(|slash| {
343 let slash = u16::from(slash) as usize;
344 ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
345 })
346 }
347
348 pub fn to_bare(&self) -> BareJid {
350 BareJid::from_parts(self.node(), self.domain())
351 }
352
353 pub fn into_bare(mut self) -> BareJid {
362 if let Some(slash) = self.slash {
363 self.normalized.truncate(slash.get() as usize);
365 self.slash = None;
366 }
367 BareJid { inner: self }
368 }
369
370 pub fn is_full(&self) -> bool {
372 self.slash.is_some()
373 }
374
375 pub fn is_bare(&self) -> bool {
377 self.slash.is_none()
378 }
379
380 pub fn as_str(&self) -> &str {
382 &self.normalized
383 }
384
385 pub fn try_into_full(self) -> Result<FullJid, BareJid> {
399 if self.slash.is_some() {
400 Ok(FullJid { inner: self })
401 } else {
402 Err(BareJid { inner: self })
403 }
404 }
405
406 pub fn try_as_full(&self) -> Result<&FullJid, &BareJid> {
420 if self.slash.is_some() {
421 Ok(unsafe {
422 mem::transmute::<&Jid, &FullJid>(self)
425 })
426 } else {
427 Err(unsafe {
428 mem::transmute::<&Jid, &BareJid>(self)
431 })
432 }
433 }
434
435 pub fn try_as_full_mut(&mut self) -> Result<&mut FullJid, &mut BareJid> {
439 if self.slash.is_some() {
440 Ok(unsafe {
441 mem::transmute::<&mut Jid, &mut FullJid>(self)
444 })
445 } else {
446 Err(unsafe {
447 mem::transmute::<&mut Jid, &mut BareJid>(self)
450 })
451 }
452 }
453
454 #[doc(hidden)]
455 #[allow(non_snake_case)]
456 #[deprecated(
457 since = "0.11.0",
458 note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
459 )]
460 pub fn Bare(other: BareJid) -> Self {
461 Self::from(other)
462 }
463
464 #[doc(hidden)]
465 #[allow(non_snake_case)]
466 #[deprecated(
467 since = "0.11.0",
468 note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
469 )]
470 pub fn Full(other: FullJid) -> Self {
471 Self::from(other)
472 }
473}
474
475impl TryFrom<Jid> for FullJid {
476 type Error = Error;
477
478 fn try_from(inner: Jid) -> Result<Self, Self::Error> {
479 if inner.slash.is_none() {
480 return Err(Error::ResourceMissingInFullJid);
481 }
482 Ok(Self { inner })
483 }
484}
485
486impl TryFrom<Jid> for BareJid {
487 type Error = Error;
488
489 fn try_from(inner: Jid) -> Result<Self, Self::Error> {
490 if inner.slash.is_some() {
491 return Err(Error::ResourceInBareJid);
492 }
493 Ok(Self { inner })
494 }
495}
496
497impl PartialEq<Jid> for FullJid {
498 fn eq(&self, other: &Jid) -> bool {
499 &self.inner == other
500 }
501}
502
503impl PartialEq<Jid> for BareJid {
504 fn eq(&self, other: &Jid) -> bool {
505 &self.inner == other
506 }
507}
508
509impl PartialEq<FullJid> for Jid {
510 fn eq(&self, other: &FullJid) -> bool {
511 self == &other.inner
512 }
513}
514
515impl PartialEq<BareJid> for Jid {
516 fn eq(&self, other: &BareJid) -> bool {
517 self == &other.inner
518 }
519}
520
521#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
533#[repr(transparent)] pub struct FullJid {
535 inner: Jid,
536}
537
538#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
547#[repr(transparent)] pub struct BareJid {
549 inner: Jid,
550}
551
552impl Deref for FullJid {
553 type Target = Jid;
554
555 fn deref(&self) -> &Self::Target {
556 &self.inner
557 }
558}
559
560impl Deref for BareJid {
561 type Target = Jid;
562
563 fn deref(&self) -> &Self::Target {
564 &self.inner
565 }
566}
567
568impl Borrow<Jid> for FullJid {
569 fn borrow(&self) -> &Jid {
570 &self.inner
571 }
572}
573
574impl Borrow<Jid> for BareJid {
575 fn borrow(&self) -> &Jid {
576 &self.inner
577 }
578}
579
580impl fmt::Debug for Jid {
581 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
582 fmt.debug_tuple("Jid").field(&self.normalized).finish()
583 }
584}
585
586impl fmt::Debug for FullJid {
587 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
588 fmt.debug_tuple("FullJid")
589 .field(&self.inner.normalized)
590 .finish()
591 }
592}
593
594impl fmt::Debug for BareJid {
595 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
596 fmt.debug_tuple("BareJid")
597 .field(&self.inner.normalized)
598 .finish()
599 }
600}
601
602impl fmt::Display for FullJid {
603 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
604 fmt::Display::fmt(&self.inner, fmt)
605 }
606}
607
608impl fmt::Display for BareJid {
609 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
610 fmt::Display::fmt(&self.inner, fmt)
611 }
612}
613
614#[cfg(feature = "serde")]
615impl Serialize for Jid {
616 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
617 where
618 S: Serializer,
619 {
620 serializer.serialize_str(&self.normalized)
621 }
622}
623
624#[cfg(feature = "serde")]
625impl Serialize for FullJid {
626 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
627 where
628 S: Serializer,
629 {
630 self.inner.serialize(serializer)
631 }
632}
633
634#[cfg(feature = "serde")]
635impl Serialize for BareJid {
636 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
637 where
638 S: Serializer,
639 {
640 self.inner.serialize(serializer)
641 }
642}
643
644impl FromStr for FullJid {
645 type Err = Error;
646
647 fn from_str(s: &str) -> Result<Self, Self::Err> {
648 Self::new(s)
649 }
650}
651
652#[cfg(feature = "serde")]
653impl<'de> Deserialize<'de> for Jid {
654 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
655 where
656 D: Deserializer<'de>,
657 {
658 let s = String::deserialize(deserializer)?;
659 Jid::new(&s).map_err(de::Error::custom)
660 }
661}
662
663#[cfg(feature = "serde")]
664impl<'de> Deserialize<'de> for FullJid {
665 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
666 where
667 D: Deserializer<'de>,
668 {
669 let jid = Jid::deserialize(deserializer)?;
670 jid.try_into().map_err(de::Error::custom)
671 }
672}
673
674#[cfg(feature = "serde")]
675impl<'de> Deserialize<'de> for BareJid {
676 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
677 where
678 D: Deserializer<'de>,
679 {
680 let jid = Jid::deserialize(deserializer)?;
681 jid.try_into().map_err(de::Error::custom)
682 }
683}
684
685#[cfg(feature = "quote")]
686impl ToTokens for Jid {
687 fn to_tokens(&self, tokens: &mut TokenStream) {
688 let s = &self.normalized;
689 tokens.extend(quote! {
690 ::jid::Jid::new(#s).unwrap()
691 });
692 }
693}
694
695#[cfg(feature = "quote")]
696impl ToTokens for FullJid {
697 fn to_tokens(&self, tokens: &mut TokenStream) {
698 let s = &self.inner.normalized;
699 tokens.extend(quote! {
700 ::jid::FullJid::new(#s).unwrap()
701 });
702 }
703}
704
705#[cfg(feature = "quote")]
706impl ToTokens for BareJid {
707 fn to_tokens(&self, tokens: &mut TokenStream) {
708 let s = &self.inner.normalized;
709 tokens.extend(quote! {
710 ::jid::BareJid::new(#s).unwrap()
711 });
712 }
713}
714
715impl FullJid {
716 pub fn new(unnormalized: &str) -> Result<Self, Error> {
736 Jid::new(unnormalized)?.try_into()
737 }
738
739 pub fn from_parts(
743 node: Option<&NodeRef>,
744 domain: &DomainRef,
745 resource: &ResourceRef,
746 ) -> FullJid {
747 let (at, slash, normalized);
748
749 if let Some(node) = node {
750 at = NonZeroU16::new(node.len() as u16);
752 slash = NonZeroU16::new((node.len() + 1 + domain.len()) as u16);
753 normalized = format!("{node}@{domain}/{resource}");
754 } else {
755 at = None;
756 slash = NonZeroU16::new(domain.len() as u16);
757 normalized = format!("{domain}/{resource}");
758 }
759
760 let inner = Jid {
761 normalized,
762 at,
763 slash,
764 };
765
766 Self { inner }
767 }
768
769 pub fn resource(&self) -> &ResourceRef {
771 self.inner.resource().unwrap()
772 }
773
774 pub fn into_inner(self) -> String {
776 self.inner.into_inner()
777 }
778
779 pub fn into_bare(self) -> BareJid {
789 self.inner.into_bare()
790 }
791}
792
793impl FromStr for BareJid {
794 type Err = Error;
795
796 fn from_str(s: &str) -> Result<Self, Self::Err> {
797 Self::new(s)
798 }
799}
800
801impl BareJid {
802 pub fn new(unnormalized: &str) -> Result<Self, Error> {
821 Jid::new(unnormalized)?.try_into()
822 }
823
824 pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> Self {
831 let (at, normalized);
832
833 if let Some(node) = node {
834 at = NonZeroU16::new(node.len() as u16);
836 normalized = format!("{node}@{domain}");
837 } else {
838 at = None;
839 normalized = domain.to_string();
840 }
841
842 let inner = Jid {
843 normalized,
844 at,
845 slash: None,
846 };
847
848 Self { inner }
849 }
850
851 pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
868 let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
869 let normalized = format!("{}/{resource}", self.inner.normalized);
870 let inner = Jid {
871 normalized,
872 at: self.inner.at,
873 slash,
874 };
875
876 FullJid { inner }
877 }
878
879 pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
895 let resource = ResourcePart::new(resource)?;
896 Ok(self.with_resource(&resource))
897 }
898
899 pub fn into_inner(self) -> String {
901 self.inner.into_inner()
902 }
903}
904
905#[cfg(feature = "minidom")]
906impl IntoAttributeValue for Jid {
907 fn into_attribute_value(self) -> Option<String> {
908 Some(self.to_string())
909 }
910}
911
912#[cfg(feature = "minidom")]
913impl From<Jid> for Node {
914 fn from(jid: Jid) -> Self {
915 Node::Text(jid.to_string())
916 }
917}
918
919#[cfg(feature = "minidom")]
920impl IntoAttributeValue for FullJid {
921 fn into_attribute_value(self) -> Option<String> {
922 self.inner.into_attribute_value()
923 }
924}
925
926#[cfg(feature = "minidom")]
927impl From<FullJid> for Node {
928 fn from(jid: FullJid) -> Self {
929 jid.inner.into()
930 }
931}
932
933#[cfg(feature = "minidom")]
934impl IntoAttributeValue for BareJid {
935 fn into_attribute_value(self) -> Option<String> {
936 self.inner.into_attribute_value()
937 }
938}
939
940#[cfg(feature = "minidom")]
941impl From<BareJid> for Node {
942 fn from(other: BareJid) -> Self {
943 other.inner.into()
944 }
945}
946
947#[cfg(test)]
948mod tests {
949 use super::*;
950
951 use alloc::{
952 collections::{BTreeMap, BTreeSet},
953 vec::Vec,
954 };
955
956 macro_rules! assert_size (
957 ($t:ty, $sz:expr) => (
958 assert_eq!(::core::mem::size_of::<$t>(), $sz);
959 );
960 );
961
962 #[cfg(target_pointer_width = "32")]
963 #[test]
964 fn test_size() {
965 assert_size!(BareJid, 16);
966 assert_size!(FullJid, 16);
967 assert_size!(Jid, 16);
968 }
969
970 #[cfg(target_pointer_width = "64")]
971 #[test]
972 fn test_size() {
973 assert_size!(BareJid, 32);
974 assert_size!(FullJid, 32);
975 assert_size!(Jid, 32);
976 }
977
978 #[test]
979 fn can_parse_full_jids() {
980 assert_eq!(
981 FullJid::from_str("a@b.c/d"),
982 Ok(FullJid::new("a@b.c/d").unwrap())
983 );
984 assert_eq!(
985 FullJid::from_str("b.c/d"),
986 Ok(FullJid::new("b.c/d").unwrap())
987 );
988
989 assert_eq!(
990 FullJid::from_str("a@b.c"),
991 Err(Error::ResourceMissingInFullJid)
992 );
993 assert_eq!(
994 FullJid::from_str("b.c"),
995 Err(Error::ResourceMissingInFullJid)
996 );
997 }
998
999 #[test]
1000 fn can_parse_bare_jids() {
1001 assert_eq!(
1002 BareJid::from_str("a@b.c"),
1003 Ok(BareJid::new("a@b.c").unwrap())
1004 );
1005 assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
1006 }
1007
1008 #[test]
1009 fn can_parse_jids() {
1010 let full = FullJid::from_str("a@b.c/d").unwrap();
1011 let bare = BareJid::from_str("e@f.g").unwrap();
1012
1013 assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
1014 assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
1015 }
1016
1017 #[test]
1018 fn full_to_bare_jid() {
1019 let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
1020 assert_eq!(bare, BareJid::new("a@b.c").unwrap());
1021 }
1022
1023 #[test]
1024 fn bare_to_full_jid_str() {
1025 assert_eq!(
1026 BareJid::new("a@b.c")
1027 .unwrap()
1028 .with_resource_str("d")
1029 .unwrap(),
1030 FullJid::new("a@b.c/d").unwrap()
1031 );
1032 }
1033
1034 #[test]
1035 fn bare_to_full_jid() {
1036 assert_eq!(
1037 BareJid::new("a@b.c")
1038 .unwrap()
1039 .with_resource(&ResourcePart::new("d").unwrap()),
1040 FullJid::new("a@b.c/d").unwrap()
1041 )
1042 }
1043
1044 #[test]
1045 fn node_from_jid() {
1046 let jid = Jid::new("a@b.c/d").unwrap();
1047
1048 assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
1049 }
1050
1051 #[test]
1052 fn domain_from_jid() {
1053 let jid = Jid::new("a@b.c").unwrap();
1054
1055 assert_eq!(jid.domain().as_str(), "b.c");
1056 }
1057
1058 #[test]
1059 fn resource_from_jid() {
1060 let jid = Jid::new("a@b.c/d").unwrap();
1061
1062 assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
1063 }
1064
1065 #[test]
1066 fn jid_to_full_bare() {
1067 let full = FullJid::new("a@b.c/d").unwrap();
1068 let bare = BareJid::new("a@b.c").unwrap();
1069
1070 assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
1071 assert_eq!(
1072 FullJid::try_from(Jid::from(bare.clone())),
1073 Err(Error::ResourceMissingInFullJid),
1074 );
1075 assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
1076 assert_eq!(Jid::from(bare.clone()), bare);
1077 }
1078
1079 #[test]
1080 fn serialise() {
1081 assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1082 assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1083 }
1084
1085 #[test]
1086 fn hash() {
1087 let _map: BTreeMap<Jid, String> = BTreeMap::new();
1088 }
1089
1090 #[test]
1091 fn invalid_jids() {
1092 assert_eq!(BareJid::from_str(""), Err(Error::Idna));
1093 assert_eq!(BareJid::from_str("/c"), Err(Error::Idna));
1094 assert_eq!(BareJid::from_str("a@/c"), Err(Error::Idna));
1095 assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
1096 assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
1097
1098 assert_eq!(FullJid::from_str(""), Err(Error::Idna));
1099 assert_eq!(FullJid::from_str("/c"), Err(Error::Idna));
1100 assert_eq!(FullJid::from_str("a@/c"), Err(Error::Idna));
1101 assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
1102 assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
1103 assert_eq!(
1104 FullJid::from_str("a@b"),
1105 Err(Error::ResourceMissingInFullJid)
1106 );
1107 assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
1108 assert_eq!(BareJid::from_str("a@b@c"), Err(Error::TooManyAts));
1109 assert_eq!(FullJid::from_str("a@b@c/d"), Err(Error::TooManyAts));
1110 }
1111
1112 #[test]
1113 fn display_jids() {
1114 assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1115 assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1116 assert_eq!(
1117 Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
1118 "a@b/c"
1119 );
1120 assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
1121 }
1122
1123 #[cfg(feature = "minidom")]
1124 #[test]
1125 fn minidom() {
1126 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1127 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1128 assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));
1129
1130 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1131 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1132 assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));
1133
1134 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1135 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
1136 assert_eq!(to, FullJid::new("a@b/c").unwrap());
1137
1138 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1139 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
1140 assert_eq!(to, BareJid::new("a@b").unwrap());
1141 }
1142
1143 #[cfg(feature = "minidom")]
1144 #[test]
1145 fn minidom_into_attr() {
1146 let full = FullJid::new("a@b/c").unwrap();
1147 let elem = minidom::Element::builder("message", "jabber:client")
1148 .attr("from", full.clone())
1149 .build();
1150 assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
1151
1152 let bare = BareJid::new("a@b").unwrap();
1153 let elem = minidom::Element::builder("message", "jabber:client")
1154 .attr("from", bare.clone())
1155 .build();
1156 assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1157
1158 let jid = Jid::from(bare.clone());
1159 let _elem = minidom::Element::builder("message", "jabber:client")
1160 .attr("from", jid)
1161 .build();
1162 assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1163 }
1164
1165 #[test]
1166 fn stringprep() {
1167 let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
1168 let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
1169 assert_eq!(full, equiv);
1170 }
1171
1172 #[test]
1173 fn invalid_stringprep() {
1174 FullJid::from_str("a@b/🎉").unwrap_err();
1175 }
1176
1177 #[test]
1178 fn idna() {
1179 let bare = BareJid::from_str("Weiß.com.").unwrap();
1180 let equiv = BareJid::new("weiss.com").unwrap();
1181 assert_eq!(bare, equiv);
1182 BareJid::from_str("127.0.0.1").unwrap();
1183 BareJid::from_str("[::1]").unwrap();
1184 BareJid::from_str("domain.tld.").unwrap();
1185 }
1186
1187 #[test]
1188 fn invalid_idna() {
1189 BareJid::from_str("a@b@c").unwrap_err();
1190 FullJid::from_str("a@b@c/d").unwrap_err();
1191 BareJid::from_str("[::1234").unwrap_err();
1192 BareJid::from_str("1::1234]").unwrap_err();
1193 BareJid::from_str("domain.tld:5222").unwrap_err();
1194 BareJid::from_str("-domain.tld").unwrap_err();
1195 BareJid::from_str("domain.tld-").unwrap_err();
1196 BareJid::from_str("domain..tld").unwrap_err();
1197 BareJid::from_str("domain.tld..").unwrap_err();
1198 BareJid::from_str("1234567890123456789012345678901234567890123456789012345678901234.com")
1199 .unwrap_err();
1200 }
1201
1202 #[test]
1203 fn jid_from_parts() {
1204 let node = NodePart::new("node").unwrap();
1205 let domain = DomainPart::new("domain").unwrap();
1206 let resource = ResourcePart::new("resource").unwrap();
1207
1208 let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
1209 assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
1210
1211 let barejid = BareJid::from_parts(Some(&node), &domain);
1212 assert_eq!(barejid, BareJid::new("node@domain").unwrap());
1213
1214 let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
1215 assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
1216 }
1217
1218 #[test]
1219 #[cfg(feature = "serde")]
1220 fn jid_ser_de() {
1221 let jid: Jid = Jid::new("node@domain").unwrap();
1222 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1223
1224 let jid: Jid = Jid::new("node@domain/resource").unwrap();
1225 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1226
1227 let jid: BareJid = BareJid::new("node@domain").unwrap();
1228 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1229
1230 let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
1231 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1232 }
1233
1234 #[test]
1235 fn jid_into_parts_and_from_parts() {
1236 let node = NodePart::new("node").unwrap();
1237 let domain = DomainPart::new("domain").unwrap();
1238
1239 let jid1 = domain.with_node(&node);
1240 let jid2 = node.with_domain(&domain);
1241 let jid3 = BareJid::new("node@domain").unwrap();
1242 assert_eq!(jid1, jid2);
1243 assert_eq!(jid2, jid3);
1244 }
1245
1246 #[test]
1247 fn jid_match_replacement_try_as() {
1248 let jid1 = Jid::new("foo@bar").unwrap();
1249 let jid2 = Jid::new("foo@bar/baz").unwrap();
1250
1251 match jid1.try_as_full() {
1252 Err(_) => (),
1253 other => panic!("unexpected result: {:?}", other),
1254 };
1255
1256 match jid2.try_as_full() {
1257 Ok(_) => (),
1258 other => panic!("unexpected result: {:?}", other),
1259 };
1260 }
1261
1262 #[test]
1263 fn jid_match_replacement_try_as_mut() {
1264 let mut jid1 = Jid::new("foo@bar").unwrap();
1265 let mut jid2 = Jid::new("foo@bar/baz").unwrap();
1266
1267 match jid1.try_as_full_mut() {
1268 Err(_) => (),
1269 other => panic!("unexpected result: {:?}", other),
1270 };
1271
1272 match jid2.try_as_full_mut() {
1273 Ok(_) => (),
1274 other => panic!("unexpected result: {:?}", other),
1275 };
1276 }
1277
1278 #[test]
1279 fn jid_match_replacement_try_into() {
1280 let jid1 = Jid::new("foo@bar").unwrap();
1281 let jid2 = Jid::new("foo@bar/baz").unwrap();
1282
1283 match jid1.try_as_full() {
1284 Err(_) => (),
1285 other => panic!("unexpected result: {:?}", other),
1286 };
1287
1288 match jid2.try_as_full() {
1289 Ok(_) => (),
1290 other => panic!("unexpected result: {:?}", other),
1291 };
1292 }
1293
1294 #[test]
1295 fn lookup_jid_by_full_jid() {
1296 let mut map: BTreeSet<Jid> = BTreeSet::new();
1297 let jid1 = Jid::new("foo@bar").unwrap();
1298 let jid2 = Jid::new("foo@bar/baz").unwrap();
1299 let jid3 = FullJid::new("foo@bar/baz").unwrap();
1300
1301 map.insert(jid1);
1302 assert!(!map.contains(&jid2));
1303 assert!(!map.contains(&jid3));
1304 map.insert(jid2);
1305 assert!(map.contains(&jid3));
1306 }
1307
1308 #[test]
1309 fn lookup_full_jid_by_jid() {
1310 let mut map: BTreeSet<FullJid> = BTreeSet::new();
1311 let jid1 = FullJid::new("foo@bar/baz").unwrap();
1312 let jid2 = FullJid::new("foo@bar/fnord").unwrap();
1313 let jid3 = Jid::new("foo@bar/fnord").unwrap();
1314
1315 map.insert(jid1);
1316 assert!(!map.contains(&jid2));
1317 assert!(!map.contains(&jid3));
1318 map.insert(jid2);
1319 assert!(map.contains(&jid3));
1320 }
1321
1322 #[test]
1323 fn lookup_bare_jid_by_jid() {
1324 let mut map: BTreeSet<BareJid> = BTreeSet::new();
1325 let jid1 = BareJid::new("foo@bar").unwrap();
1326 let jid2 = BareJid::new("foo@baz").unwrap();
1327 let jid3 = Jid::new("foo@baz").unwrap();
1328
1329 map.insert(jid1);
1330 assert!(!map.contains(&jid2));
1331 assert!(!map.contains(&jid3));
1332 map.insert(jid2);
1333 assert!(map.contains(&jid3));
1334 }
1335
1336 #[test]
1337 fn normalizes_all_parts() {
1338 assert_eq!(
1339 Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
1340 "ssa@ix.test/IX"
1341 );
1342 }
1343
1344 #[test]
1345 fn rejects_unassigned_codepoints() {
1346 match Jid::new("\u{01f601}@example.com") {
1347 Err(Error::NodePrep) => (),
1348 other => panic!("unexpected result: {:?}", other),
1349 };
1350
1351 match Jid::new("foo@\u{01f601}.example.com") {
1352 Err(Error::NamePrep) => (),
1353 other => panic!("unexpected result: {:?}", other),
1354 };
1355
1356 match Jid::new("foo@example.com/\u{01f601}") {
1357 Err(Error::ResourcePrep) => (),
1358 other => panic!("unexpected result: {:?}", other),
1359 };
1360 }
1361
1362 #[test]
1363 fn accepts_domain_only_jid() {
1364 match Jid::new("example.com") {
1365 Ok(_) => (),
1366 other => panic!("unexpected result: {:?}", other),
1367 };
1368
1369 match BareJid::new("example.com") {
1370 Ok(_) => (),
1371 other => panic!("unexpected result: {:?}", other),
1372 };
1373
1374 match FullJid::new("example.com/x") {
1375 Ok(_) => (),
1376 other => panic!("unexpected result: {:?}", other),
1377 };
1378 }
1379
1380 #[test]
1381 fn is_bare_returns_true_iff_bare() {
1382 let bare = Jid::new("foo@bar").unwrap();
1383 let full = Jid::new("foo@bar/baz").unwrap();
1384
1385 assert!(bare.is_bare());
1386 assert!(!full.is_bare());
1387 }
1388
1389 #[test]
1390 fn is_full_returns_true_iff_full() {
1391 let bare = Jid::new("foo@bar").unwrap();
1392 let full = Jid::new("foo@bar/baz").unwrap();
1393
1394 assert!(!bare.is_full());
1395 assert!(full.is_full());
1396 }
1397
1398 #[test]
1399 fn reject_long_localpart() {
1400 let mut long = Vec::with_capacity(1028);
1401 long.resize(1024, b'a');
1402 let mut long = String::from_utf8(long).unwrap();
1403 long.push_str("@foo");
1404
1405 match Jid::new(&long) {
1406 Err(Error::NodeTooLong) => (),
1407 other => panic!("unexpected result: {:?}", other),
1408 }
1409
1410 match BareJid::new(&long) {
1411 Err(Error::NodeTooLong) => (),
1412 other => panic!("unexpected result: {:?}", other),
1413 }
1414 }
1415
1416 #[test]
1417 fn reject_long_domainpart() {
1418 let mut long = Vec::with_capacity(66);
1419 long.push(b'x');
1420 long.push(b'@');
1421 long.resize(66, b'a');
1422 let long = String::from_utf8(long).unwrap();
1423
1424 Jid::new(&long).unwrap_err();
1425 BareJid::new(&long).unwrap_err();
1426
1427 let mut long = Vec::with_capacity(256);
1429 long.push(b'x');
1430 long.push(b'@');
1431 long.resize(65, b'a');
1432 long.push(b'.');
1433 long.resize(129, b'b');
1434 long.push(b'.');
1435 long.resize(193, b'c');
1436 long.push(b'.');
1437 long.resize(256, b'd');
1438 let long = String::from_utf8(long).unwrap();
1439
1440 Jid::new(&long).unwrap_err();
1441 BareJid::new(&long).unwrap_err();
1442 }
1443
1444 #[test]
1445 fn reject_long_resourcepart() {
1446 let mut long = Vec::with_capacity(1028);
1447 long.push(b'x');
1448 long.push(b'@');
1449 long.push(b'y');
1450 long.push(b'/');
1451 long.resize(1028, b'a');
1452 let long = String::from_utf8(long).unwrap();
1453
1454 match Jid::new(&long) {
1455 Err(Error::ResourceTooLong) => (),
1456 other => panic!("unexpected result: {:?}", other),
1457 }
1458
1459 match FullJid::new(&long) {
1460 Err(Error::ResourceTooLong) => (),
1461 other => panic!("unexpected result: {:?}", other),
1462 }
1463 }
1464}