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