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