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::new("a@b.c").unwrap())
1005 );
1006 assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
1007 }
1008
1009 #[test]
1010 fn can_parse_jids() {
1011 let full = FullJid::from_str("a@b.c/d").unwrap();
1012 let bare = BareJid::from_str("e@f.g").unwrap();
1013
1014 assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
1015 assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
1016 }
1017
1018 #[test]
1019 fn full_to_bare_jid() {
1020 let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
1021 assert_eq!(bare, BareJid::new("a@b.c").unwrap());
1022 }
1023
1024 #[test]
1025 fn bare_to_full_jid_str() {
1026 assert_eq!(
1027 BareJid::new("a@b.c")
1028 .unwrap()
1029 .with_resource_str("d")
1030 .unwrap(),
1031 FullJid::new("a@b.c/d").unwrap()
1032 );
1033 }
1034
1035 #[test]
1036 fn bare_to_full_jid() {
1037 assert_eq!(
1038 BareJid::new("a@b.c")
1039 .unwrap()
1040 .with_resource(&ResourcePart::new("d").unwrap()),
1041 FullJid::new("a@b.c/d").unwrap()
1042 )
1043 }
1044
1045 #[test]
1046 fn node_from_jid() {
1047 let jid = Jid::new("a@b.c/d").unwrap();
1048
1049 assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
1050 }
1051
1052 #[test]
1053 fn domain_from_jid() {
1054 let jid = Jid::new("a@b.c").unwrap();
1055
1056 assert_eq!(jid.domain().as_str(), "b.c");
1057 }
1058
1059 #[test]
1060 fn resource_from_jid() {
1061 let jid = Jid::new("a@b.c/d").unwrap();
1062
1063 assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
1064 }
1065
1066 #[test]
1067 fn jid_to_full_bare() {
1068 let full = FullJid::new("a@b.c/d").unwrap();
1069 let bare = BareJid::new("a@b.c").unwrap();
1070
1071 assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
1072 assert_eq!(
1073 FullJid::try_from(Jid::from(bare.clone())),
1074 Err(Error::ResourceMissingInFullJid),
1075 );
1076 assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
1077 assert_eq!(Jid::from(bare.clone()), bare);
1078 }
1079
1080 #[test]
1081 fn serialise() {
1082 assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1083 assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1084 }
1085
1086 #[test]
1087 fn hash() {
1088 let _map: BTreeMap<Jid, String> = BTreeMap::new();
1089 }
1090
1091 #[test]
1092 fn invalid_jids() {
1093 assert_eq!(BareJid::from_str(""), Err(Error::Idna));
1094 assert_eq!(BareJid::from_str("/c"), Err(Error::Idna));
1095 assert_eq!(BareJid::from_str("a@/c"), Err(Error::Idna));
1096 assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
1097 assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));
1098
1099 assert_eq!(FullJid::from_str(""), Err(Error::Idna));
1100 assert_eq!(FullJid::from_str("/c"), Err(Error::Idna));
1101 assert_eq!(FullJid::from_str("a@/c"), Err(Error::Idna));
1102 assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
1103 assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
1104 assert_eq!(
1105 FullJid::from_str("a@b"),
1106 Err(Error::ResourceMissingInFullJid)
1107 );
1108 assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
1109 assert_eq!(BareJid::from_str("a@b@c"), Err(Error::TooManyAts));
1110 assert_eq!(FullJid::from_str("a@b@c/d"), Err(Error::TooManyAts));
1111 }
1112
1113 #[test]
1114 fn display_jids() {
1115 assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
1116 assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
1117 assert_eq!(
1118 Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
1119 "a@b/c"
1120 );
1121 assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
1122 }
1123
1124 #[cfg(feature = "minidom")]
1125 #[test]
1126 fn minidom() {
1127 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1128 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1129 assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));
1130
1131 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1132 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
1133 assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));
1134
1135 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
1136 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
1137 assert_eq!(to, FullJid::new("a@b/c").unwrap());
1138
1139 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
1140 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
1141 assert_eq!(to, BareJid::new("a@b").unwrap());
1142 }
1143
1144 #[cfg(feature = "minidom")]
1145 #[test]
1146 fn minidom_into_attr() {
1147 let full = FullJid::new("a@b/c").unwrap();
1148 let elem = minidom::Element::builder("message", "jabber:client")
1149 .attr("from", full.clone())
1150 .build();
1151 assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));
1152
1153 let bare = BareJid::new("a@b").unwrap();
1154 let elem = minidom::Element::builder("message", "jabber:client")
1155 .attr("from", bare.clone())
1156 .build();
1157 assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1158
1159 let jid = Jid::from(bare.clone());
1160 let _elem = minidom::Element::builder("message", "jabber:client")
1161 .attr("from", jid)
1162 .build();
1163 assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
1164 }
1165
1166 #[test]
1167 fn stringprep() {
1168 let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
1169 let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
1170 assert_eq!(full, equiv);
1171 }
1172
1173 #[test]
1174 fn invalid_stringprep() {
1175 FullJid::from_str("a@b/🎉").unwrap_err();
1176 }
1177
1178 #[test]
1179 fn idna() {
1180 let bare = BareJid::from_str("Weiß.com.").unwrap();
1181 let equiv = BareJid::new("weiss.com").unwrap();
1182 assert_eq!(bare, equiv);
1183 BareJid::from_str("127.0.0.1").unwrap();
1184 BareJid::from_str("[::1]").unwrap();
1185 BareJid::from_str("domain.tld.").unwrap();
1186 }
1187
1188 #[test]
1189 fn invalid_idna() {
1190 BareJid::from_str("a@b@c").unwrap_err();
1191 FullJid::from_str("a@b@c/d").unwrap_err();
1192 BareJid::from_str("[::1234").unwrap_err();
1193 BareJid::from_str("1::1234]").unwrap_err();
1194 BareJid::from_str("domain.tld:5222").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("domain.tld..").unwrap_err();
1199 BareJid::from_str("1234567890123456789012345678901234567890123456789012345678901234.com")
1200 .unwrap_err();
1201 }
1202
1203 #[test]
1204 fn jid_from_parts() {
1205 let node = NodePart::new("node").unwrap();
1206 let domain = DomainPart::new("domain").unwrap();
1207 let resource = ResourcePart::new("resource").unwrap();
1208
1209 let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
1210 assert_eq!(jid, Jid::new("node@domain/resource").unwrap());
1211
1212 let barejid = BareJid::from_parts(Some(&node), &domain);
1213 assert_eq!(barejid, BareJid::new("node@domain").unwrap());
1214
1215 let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
1216 assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
1217 }
1218
1219 #[test]
1220 #[cfg(feature = "serde")]
1221 fn jid_ser_de() {
1222 let jid: Jid = Jid::new("node@domain").unwrap();
1223 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1224
1225 let jid: Jid = Jid::new("node@domain/resource").unwrap();
1226 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1227
1228 let jid: BareJid = BareJid::new("node@domain").unwrap();
1229 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);
1230
1231 let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
1232 serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
1233 }
1234
1235 #[test]
1236 fn jid_into_parts_and_from_parts() {
1237 let node = NodePart::new("node").unwrap();
1238 let domain = DomainPart::new("domain").unwrap();
1239
1240 let jid1 = domain.with_node(&node);
1241 let jid2 = node.with_domain(&domain);
1242 let jid3 = BareJid::new("node@domain").unwrap();
1243 assert_eq!(jid1, jid2);
1244 assert_eq!(jid2, jid3);
1245 }
1246
1247 #[test]
1248 fn jid_match_replacement_try_as() {
1249 let jid1 = Jid::new("foo@bar").unwrap();
1250 let jid2 = Jid::new("foo@bar/baz").unwrap();
1251
1252 match jid1.try_as_full() {
1253 Err(_) => (),
1254 other => panic!("unexpected result: {:?}", other),
1255 };
1256
1257 match jid2.try_as_full() {
1258 Ok(_) => (),
1259 other => panic!("unexpected result: {:?}", other),
1260 };
1261 }
1262
1263 #[test]
1264 fn jid_match_replacement_try_as_mut() {
1265 let mut jid1 = Jid::new("foo@bar").unwrap();
1266 let mut jid2 = Jid::new("foo@bar/baz").unwrap();
1267
1268 match jid1.try_as_full_mut() {
1269 Err(_) => (),
1270 other => panic!("unexpected result: {:?}", other),
1271 };
1272
1273 match jid2.try_as_full_mut() {
1274 Ok(_) => (),
1275 other => panic!("unexpected result: {:?}", other),
1276 };
1277 }
1278
1279 #[test]
1280 fn jid_match_replacement_try_into() {
1281 let jid1 = Jid::new("foo@bar").unwrap();
1282 let jid2 = Jid::new("foo@bar/baz").unwrap();
1283
1284 match jid1.try_as_full() {
1285 Err(_) => (),
1286 other => panic!("unexpected result: {:?}", other),
1287 };
1288
1289 match jid2.try_as_full() {
1290 Ok(_) => (),
1291 other => panic!("unexpected result: {:?}", other),
1292 };
1293 }
1294
1295 #[test]
1296 fn lookup_jid_by_full_jid() {
1297 let mut map: BTreeSet<Jid> = BTreeSet::new();
1298 let jid1 = Jid::new("foo@bar").unwrap();
1299 let jid2 = Jid::new("foo@bar/baz").unwrap();
1300 let jid3 = FullJid::new("foo@bar/baz").unwrap();
1301
1302 map.insert(jid1);
1303 assert!(!map.contains(&jid2));
1304 assert!(!map.contains(&jid3));
1305 map.insert(jid2);
1306 assert!(map.contains(&jid3));
1307 }
1308
1309 #[test]
1310 fn lookup_full_jid_by_jid() {
1311 let mut map: BTreeSet<FullJid> = BTreeSet::new();
1312 let jid1 = FullJid::new("foo@bar/baz").unwrap();
1313 let jid2 = FullJid::new("foo@bar/fnord").unwrap();
1314 let jid3 = Jid::new("foo@bar/fnord").unwrap();
1315
1316 map.insert(jid1);
1317 assert!(!map.contains(&jid2));
1318 assert!(!map.contains(&jid3));
1319 map.insert(jid2);
1320 assert!(map.contains(&jid3));
1321 }
1322
1323 #[test]
1324 fn lookup_bare_jid_by_jid() {
1325 let mut map: BTreeSet<BareJid> = BTreeSet::new();
1326 let jid1 = BareJid::new("foo@bar").unwrap();
1327 let jid2 = BareJid::new("foo@baz").unwrap();
1328 let jid3 = Jid::new("foo@baz").unwrap();
1329
1330 map.insert(jid1);
1331 assert!(!map.contains(&jid2));
1332 assert!(!map.contains(&jid3));
1333 map.insert(jid2);
1334 assert!(map.contains(&jid3));
1335 }
1336
1337 #[test]
1338 fn normalizes_all_parts() {
1339 assert_eq!(
1340 Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
1341 "ssa@ix.test/IX"
1342 );
1343 }
1344
1345 #[test]
1346 fn rejects_unassigned_codepoints() {
1347 match Jid::new("\u{01f601}@example.com") {
1348 Err(Error::NodePrep) => (),
1349 other => panic!("unexpected result: {:?}", other),
1350 };
1351
1352 match Jid::new("foo@\u{01f601}.example.com") {
1353 Err(Error::NamePrep) => (),
1354 other => panic!("unexpected result: {:?}", other),
1355 };
1356
1357 match Jid::new("foo@example.com/\u{01f601}") {
1358 Err(Error::ResourcePrep) => (),
1359 other => panic!("unexpected result: {:?}", other),
1360 };
1361 }
1362
1363 #[test]
1364 fn accepts_domain_only_jid() {
1365 match Jid::new("example.com") {
1366 Ok(_) => (),
1367 other => panic!("unexpected result: {:?}", other),
1368 };
1369
1370 match BareJid::new("example.com") {
1371 Ok(_) => (),
1372 other => panic!("unexpected result: {:?}", other),
1373 };
1374
1375 match FullJid::new("example.com/x") {
1376 Ok(_) => (),
1377 other => panic!("unexpected result: {:?}", other),
1378 };
1379 }
1380
1381 #[test]
1382 fn is_bare_returns_true_iff_bare() {
1383 let bare = Jid::new("foo@bar").unwrap();
1384 let full = Jid::new("foo@bar/baz").unwrap();
1385
1386 assert!(bare.is_bare());
1387 assert!(!full.is_bare());
1388 }
1389
1390 #[test]
1391 fn is_full_returns_true_iff_full() {
1392 let bare = Jid::new("foo@bar").unwrap();
1393 let full = Jid::new("foo@bar/baz").unwrap();
1394
1395 assert!(!bare.is_full());
1396 assert!(full.is_full());
1397 }
1398
1399 #[test]
1400 fn reject_long_localpart() {
1401 let mut long = Vec::with_capacity(1028);
1402 long.resize(1024, b'a');
1403 let mut long = String::from_utf8(long).unwrap();
1404 long.push_str("@foo");
1405
1406 match Jid::new(&long) {
1407 Err(Error::NodeTooLong) => (),
1408 other => panic!("unexpected result: {:?}", other),
1409 }
1410
1411 match BareJid::new(&long) {
1412 Err(Error::NodeTooLong) => (),
1413 other => panic!("unexpected result: {:?}", other),
1414 }
1415 }
1416
1417 #[test]
1418 fn reject_long_domainpart() {
1419 let mut long = Vec::with_capacity(66);
1420 long.push(b'x');
1421 long.push(b'@');
1422 long.resize(66, b'a');
1423 let long = String::from_utf8(long).unwrap();
1424
1425 Jid::new(&long).unwrap_err();
1426 BareJid::new(&long).unwrap_err();
1427
1428 let mut long = Vec::with_capacity(256);
1430 long.push(b'x');
1431 long.push(b'@');
1432 long.resize(65, b'a');
1433 long.push(b'.');
1434 long.resize(129, b'b');
1435 long.push(b'.');
1436 long.resize(193, b'c');
1437 long.push(b'.');
1438 long.resize(256, b'd');
1439 let long = String::from_utf8(long).unwrap();
1440
1441 Jid::new(&long).unwrap_err();
1442 BareJid::new(&long).unwrap_err();
1443 }
1444
1445 #[test]
1446 fn reject_long_resourcepart() {
1447 let mut long = Vec::with_capacity(1028);
1448 long.push(b'x');
1449 long.push(b'@');
1450 long.push(b'y');
1451 long.push(b'/');
1452 long.resize(1028, b'a');
1453 let long = String::from_utf8(long).unwrap();
1454
1455 match Jid::new(&long) {
1456 Err(Error::ResourceTooLong) => (),
1457 other => panic!("unexpected result: {:?}", other),
1458 }
1459
1460 match FullJid::new(&long) {
1461 Err(Error::ResourceTooLong) => (),
1462 other => panic!("unexpected result: {:?}", other),
1463 }
1464 }
1465}