1#![deny(missing_docs)]
12
13use std::convert::{Into, TryFrom};
18use std::error::Error as StdError;
19use std::fmt;
20use std::str::FromStr;
21
22#[cfg(feature = "serde")]
23use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum JidParseError {
28 NoDomain,
31
32 NoResource,
34
35 EmptyNode,
37
38 EmptyResource,
40}
41
42impl StdError for JidParseError {}
43
44impl fmt::Display for JidParseError {
45 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
46 write!(
47 fmt,
48 "{}",
49 match self {
50 JidParseError::NoDomain => "no domain found in this JID",
51 JidParseError::NoResource => "no resource found in this full JID",
52 JidParseError::EmptyNode => "nodepart empty despite the presence of a @",
53 JidParseError::EmptyResource => "resource empty despite the presence of a /",
54 }
55 )
56 }
57}
58
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub enum Jid {
63 Bare(BareJid),
65
66 Full(FullJid),
68}
69
70impl FromStr for Jid {
71 type Err = JidParseError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 let (ns, ds, rs): StringJid = _from_str(s)?;
75 Ok(match rs {
76 Some(rs) => Jid::Full(FullJid {
77 node: ns,
78 domain: ds,
79 resource: rs,
80 }),
81 None => Jid::Bare(BareJid {
82 node: ns,
83 domain: ds,
84 }),
85 })
86 }
87}
88
89impl From<Jid> for String {
90 fn from(jid: Jid) -> String {
91 match jid {
92 Jid::Bare(bare) => String::from(bare),
93 Jid::Full(full) => String::from(full),
94 }
95 }
96}
97
98impl From<BareJid> for Jid {
99 fn from(bare_jid: BareJid) -> Jid {
100 Jid::Bare(bare_jid)
101 }
102}
103
104impl From<FullJid> for Jid {
105 fn from(full_jid: FullJid) -> Jid {
106 Jid::Full(full_jid)
107 }
108}
109
110impl fmt::Display for Jid {
111 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
112 fmt.write_str(String::from(self.clone()).as_ref())
113 }
114}
115
116impl Jid {
117 pub fn node(self) -> Option<String> {
119 match self {
120 Jid::Bare(BareJid { node, .. }) | Jid::Full(FullJid { node, .. }) => node,
121 }
122 }
123
124 pub fn domain(self) -> String {
126 match self {
127 Jid::Bare(BareJid { domain, .. }) | Jid::Full(FullJid { domain, .. }) => domain,
128 }
129 }
130}
131
132impl From<Jid> for BareJid {
133 fn from(jid: Jid) -> BareJid {
134 match jid {
135 Jid::Full(full) => full.into(),
136 Jid::Bare(bare) => bare,
137 }
138 }
139}
140
141impl TryFrom<Jid> for FullJid {
142 type Error = JidParseError;
143
144 fn try_from(jid: Jid) -> Result<Self, Self::Error> {
145 match jid {
146 Jid::Full(full) => Ok(full),
147 Jid::Bare(_) => Err(JidParseError::NoResource),
148 }
149 }
150}
151
152impl PartialEq<Jid> for FullJid {
153 fn eq(&self, other: &Jid) -> bool {
154 match other {
155 Jid::Full(full) => self == full,
156 Jid::Bare(_) => false,
157 }
158 }
159}
160
161impl PartialEq<Jid> for BareJid {
162 fn eq(&self, other: &Jid) -> bool {
163 match other {
164 Jid::Full(_) => false,
165 Jid::Bare(bare) => self == bare,
166 }
167 }
168}
169
170impl PartialEq<FullJid> for Jid {
171 fn eq(&self, other: &FullJid) -> bool {
172 match self {
173 Jid::Full(full) => full == other,
174 Jid::Bare(_) => false,
175 }
176 }
177}
178
179impl PartialEq<BareJid> for Jid {
180 fn eq(&self, other: &BareJid) -> bool {
181 match self {
182 Jid::Full(_) => false,
183 Jid::Bare(bare) => bare == other,
184 }
185 }
186}
187
188#[derive(Clone, PartialEq, Eq, Hash)]
199pub struct FullJid {
200 pub node: Option<String>,
202 pub domain: String,
204 pub resource: String,
206}
207
208#[derive(Clone, PartialEq, Eq, Hash)]
218pub struct BareJid {
219 pub node: Option<String>,
221 pub domain: String,
223}
224
225impl From<FullJid> for String {
226 fn from(jid: FullJid) -> String {
227 String::from(&jid)
228 }
229}
230
231impl From<&FullJid> for String {
232 fn from(jid: &FullJid) -> String {
233 let mut string = String::new();
234 if let Some(ref node) = jid.node {
235 string.push_str(node);
236 string.push('@');
237 }
238 string.push_str(&jid.domain);
239 string.push('/');
240 string.push_str(&jid.resource);
241 string
242 }
243}
244
245impl From<BareJid> for String {
246 fn from(jid: BareJid) -> String {
247 String::from(&jid)
248 }
249}
250
251impl From<&BareJid> for String {
252 fn from(jid: &BareJid) -> String {
253 let mut string = String::new();
254 if let Some(ref node) = jid.node {
255 string.push_str(node);
256 string.push('@');
257 }
258 string.push_str(&jid.domain);
259 string
260 }
261}
262
263impl Into<BareJid> for FullJid {
264 fn into(self) -> BareJid {
265 BareJid {
266 node: self.node,
267 domain: self.domain,
268 }
269 }
270}
271
272impl fmt::Debug for FullJid {
273 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
274 write!(fmt, "FullJID({})", self)
275 }
276}
277
278impl fmt::Debug for BareJid {
279 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
280 write!(fmt, "BareJID({})", self)
281 }
282}
283
284impl fmt::Display for FullJid {
285 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
286 fmt.write_str(String::from(self.clone()).as_ref())
287 }
288}
289
290impl fmt::Display for BareJid {
291 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
292 fmt.write_str(String::from(self.clone()).as_ref())
293 }
294}
295
296#[cfg(feature = "serde")]
297impl Serialize for FullJid {
298 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
299 where
300 S: Serializer,
301 {
302 serializer.serialize_str(String::from(self).as_str())
303 }
304}
305
306#[cfg(feature = "serde")]
307impl Serialize for BareJid {
308 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
309 where
310 S: Serializer,
311 {
312 serializer.serialize_str(String::from(self).as_str())
313 }
314}
315
316enum ParserState {
317 Node,
318 Domain,
319 Resource,
320}
321
322type StringJid = (Option<String>, String, Option<String>);
323fn _from_str(s: &str) -> Result<StringJid, JidParseError> {
324 let iter = s.chars();
326 let mut buf = String::with_capacity(s.len());
327 let mut state = ParserState::Node;
328 let mut node = None;
329 let mut domain = None;
330 let mut resource = None;
331 for c in iter {
332 match state {
333 ParserState::Node => {
334 match c {
335 '@' => {
336 if buf == "" {
337 return Err(JidParseError::EmptyNode);
338 }
339 state = ParserState::Domain;
340 node = Some(buf.clone()); buf.clear();
342 }
343 '/' => {
344 if buf == "" {
345 return Err(JidParseError::NoDomain);
346 }
347 state = ParserState::Resource;
348 domain = Some(buf.clone()); buf.clear();
350 }
351 c => {
352 buf.push(c);
353 }
354 }
355 }
356 ParserState::Domain => {
357 match c {
358 '/' => {
359 if buf == "" {
360 return Err(JidParseError::NoDomain);
361 }
362 state = ParserState::Resource;
363 domain = Some(buf.clone()); buf.clear();
365 }
366 c => {
367 buf.push(c);
368 }
369 }
370 }
371 ParserState::Resource => {
372 buf.push(c);
373 }
374 }
375 }
376 if !buf.is_empty() {
377 match state {
378 ParserState::Node => {
379 domain = Some(buf);
380 }
381 ParserState::Domain => {
382 domain = Some(buf);
383 }
384 ParserState::Resource => {
385 resource = Some(buf);
386 }
387 }
388 } else if let ParserState::Resource = state {
389 return Err(JidParseError::EmptyResource);
390 }
391 Ok((node, domain.ok_or(JidParseError::NoDomain)?, resource))
392}
393
394impl FromStr for FullJid {
395 type Err = JidParseError;
396
397 fn from_str(s: &str) -> Result<FullJid, JidParseError> {
398 let (ns, ds, rs): StringJid = _from_str(s)?;
399 Ok(FullJid {
400 node: ns,
401 domain: ds,
402 resource: rs.ok_or(JidParseError::NoResource)?,
403 })
404 }
405}
406
407#[cfg(feature = "serde")]
408impl<'de> Deserialize<'de> for FullJid {
409 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
410 where
411 D: Deserializer<'de>,
412 {
413 let s = String::deserialize(deserializer)?;
414 FullJid::from_str(&s).map_err(de::Error::custom)
415 }
416}
417
418#[cfg(feature = "serde")]
419impl<'de> Deserialize<'de> for BareJid {
420 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
421 where
422 D: Deserializer<'de>,
423 {
424 let s = String::deserialize(deserializer)?;
425 BareJid::from_str(&s).map_err(de::Error::custom)
426 }
427}
428
429impl FullJid {
430 pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
446 where
447 NS: Into<String>,
448 DS: Into<String>,
449 RS: Into<String>,
450 {
451 FullJid {
452 node: Some(node.into()),
453 domain: domain.into(),
454 resource: resource.into(),
455 }
456 }
457
458 pub fn with_node<NS>(&self, node: NS) -> FullJid
474 where
475 NS: Into<String>,
476 {
477 FullJid {
478 node: Some(node.into()),
479 domain: self.domain.clone(),
480 resource: self.resource.clone(),
481 }
482 }
483
484 pub fn with_domain<DS>(&self, domain: DS) -> FullJid
500 where
501 DS: Into<String>,
502 {
503 FullJid {
504 node: self.node.clone(),
505 domain: domain.into(),
506 resource: self.resource.clone(),
507 }
508 }
509
510 pub fn with_resource<RS>(&self, resource: RS) -> FullJid
526 where
527 RS: Into<String>,
528 {
529 FullJid {
530 node: self.node.clone(),
531 domain: self.domain.clone(),
532 resource: resource.into(),
533 }
534 }
535}
536
537impl FromStr for BareJid {
538 type Err = JidParseError;
539
540 fn from_str(s: &str) -> Result<BareJid, JidParseError> {
541 let (ns, ds, _rs): StringJid = _from_str(s)?;
542 Ok(BareJid {
543 node: ns,
544 domain: ds,
545 })
546 }
547}
548
549impl BareJid {
550 pub fn new<NS, DS>(node: NS, domain: DS) -> BareJid
565 where
566 NS: Into<String>,
567 DS: Into<String>,
568 {
569 BareJid {
570 node: Some(node.into()),
571 domain: domain.into(),
572 }
573 }
574
575 pub fn domain<DS>(domain: DS) -> BareJid
590 where
591 DS: Into<String>,
592 {
593 BareJid {
594 node: None,
595 domain: domain.into(),
596 }
597 }
598
599 pub fn with_node<NS>(&self, node: NS) -> BareJid
615 where
616 NS: Into<String>,
617 {
618 BareJid {
619 node: Some(node.into()),
620 domain: self.domain.clone(),
621 }
622 }
623
624 pub fn with_domain<DS>(&self, domain: DS) -> BareJid
640 where
641 DS: Into<String>,
642 {
643 BareJid {
644 node: self.node.clone(),
645 domain: domain.into(),
646 }
647 }
648
649 pub fn with_resource<RS>(self, resource: RS) -> FullJid
664 where
665 RS: Into<String>,
666 {
667 FullJid {
668 node: self.node,
669 domain: self.domain,
670 resource: resource.into(),
671 }
672 }
673}
674
675#[cfg(feature = "minidom")]
676use minidom::{IntoAttributeValue, Node};
677
678#[cfg(feature = "minidom")]
679impl IntoAttributeValue for Jid {
680 fn into_attribute_value(self) -> Option<String> {
681 Some(String::from(self))
682 }
683}
684
685#[cfg(feature = "minidom")]
686impl Into<Node> for Jid {
687 fn into(self) -> Node {
688 Node::Text(String::from(self))
689 }
690}
691
692#[cfg(feature = "minidom")]
693impl IntoAttributeValue for FullJid {
694 fn into_attribute_value(self) -> Option<String> {
695 Some(String::from(self))
696 }
697}
698
699#[cfg(feature = "minidom")]
700impl Into<Node> for FullJid {
701 fn into(self) -> Node {
702 Node::Text(String::from(self))
703 }
704}
705
706#[cfg(feature = "minidom")]
707impl IntoAttributeValue for BareJid {
708 fn into_attribute_value(self) -> Option<String> {
709 Some(String::from(self))
710 }
711}
712
713#[cfg(feature = "minidom")]
714impl Into<Node> for BareJid {
715 fn into(self) -> Node {
716 Node::Text(String::from(self))
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 use std::collections::HashMap;
725 use std::str::FromStr;
726
727 #[test]
728 fn can_parse_full_jids() {
729 assert_eq!(
730 FullJid::from_str("a@b.c/d"),
731 Ok(FullJid::new("a", "b.c", "d"))
732 );
733 assert_eq!(
734 FullJid::from_str("b.c/d"),
735 Ok(FullJid {
736 node: None,
737 domain: "b.c".to_owned(),
738 resource: "d".to_owned(),
739 })
740 );
741
742 assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
743 assert_eq!(FullJid::from_str("b.c"), Err(JidParseError::NoResource));
744 }
745
746 #[test]
747 fn can_parse_bare_jids() {
748 assert_eq!(BareJid::from_str("a@b.c/d"), Ok(BareJid::new("a", "b.c")));
749 assert_eq!(
750 BareJid::from_str("b.c/d"),
751 Ok(BareJid {
752 node: None,
753 domain: "b.c".to_owned(),
754 })
755 );
756
757 assert_eq!(BareJid::from_str("a@b.c"), Ok(BareJid::new("a", "b.c")));
758 assert_eq!(
759 BareJid::from_str("b.c"),
760 Ok(BareJid {
761 node: None,
762 domain: "b.c".to_owned(),
763 })
764 );
765 }
766
767 #[test]
768 fn can_parse_jids() {
769 let full = FullJid::from_str("a@b.c/d").unwrap();
770 let bare = BareJid::from_str("e@f.g").unwrap();
771
772 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
773 assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
774 }
775
776 #[test]
777 fn full_to_bare_jid() {
778 let bare: BareJid = FullJid::new("a", "b.c", "d").into();
779 assert_eq!(bare, BareJid::new("a", "b.c"));
780 }
781
782 #[test]
783 fn bare_to_full_jid() {
784 assert_eq!(
785 BareJid::new("a", "b.c").with_resource("d"),
786 FullJid::new("a", "b.c", "d")
787 );
788 }
789
790 #[test]
791 fn node_from_jid() {
792 assert_eq!(
793 Jid::Full(FullJid::new("a", "b.c", "d")).node(),
794 Some(String::from("a")),
795 );
796 }
797
798 #[test]
799 fn domain_from_jid() {
800 assert_eq!(
801 Jid::Bare(BareJid::new("a", "b.c")).domain(),
802 String::from("b.c"),
803 );
804 }
805
806 #[test]
807 fn jid_to_full_bare() {
808 let full = FullJid::new("a", "b.c", "d");
809 let bare = BareJid::new("a", "b.c");
810
811 assert_eq!(FullJid::try_from(Jid::Full(full.clone())), Ok(full.clone()),);
812 assert_eq!(
813 FullJid::try_from(Jid::Bare(bare.clone())),
814 Err(JidParseError::NoResource),
815 );
816 assert_eq!(BareJid::from(Jid::Full(full.clone())), bare.clone(),);
817 assert_eq!(BareJid::from(Jid::Bare(bare.clone())), bare,);
818 }
819
820 #[test]
821 fn serialise() {
822 assert_eq!(
823 String::from(FullJid::new("a", "b", "c")),
824 String::from("a@b/c")
825 );
826 assert_eq!(String::from(BareJid::new("a", "b")), String::from("a@b"));
827 }
828
829 #[test]
830 fn hash() {
831 let _map: HashMap<Jid, String> = HashMap::new();
832 }
833
834 #[test]
835 fn invalid_jids() {
836 assert_eq!(BareJid::from_str(""), Err(JidParseError::NoDomain));
837 assert_eq!(BareJid::from_str("/c"), Err(JidParseError::NoDomain));
838 assert_eq!(BareJid::from_str("a@/c"), Err(JidParseError::NoDomain));
839 assert_eq!(BareJid::from_str("@b"), Err(JidParseError::EmptyNode));
840 assert_eq!(BareJid::from_str("b/"), Err(JidParseError::EmptyResource));
841
842 assert_eq!(FullJid::from_str(""), Err(JidParseError::NoDomain));
843 assert_eq!(FullJid::from_str("/c"), Err(JidParseError::NoDomain));
844 assert_eq!(FullJid::from_str("a@/c"), Err(JidParseError::NoDomain));
845 assert_eq!(FullJid::from_str("@b"), Err(JidParseError::EmptyNode));
846 assert_eq!(FullJid::from_str("b/"), Err(JidParseError::EmptyResource));
847 assert_eq!(FullJid::from_str("a@b"), Err(JidParseError::NoResource));
848 }
849
850 #[test]
851 fn display_jids() {
852 assert_eq!(
853 format!("{}", FullJid::new("a", "b", "c")),
854 String::from("a@b/c")
855 );
856 assert_eq!(format!("{}", BareJid::new("a", "b")), String::from("a@b"));
857 assert_eq!(
858 format!("{}", Jid::Full(FullJid::new("a", "b", "c"))),
859 String::from("a@b/c")
860 );
861 assert_eq!(
862 format!("{}", Jid::Bare(BareJid::new("a", "b"))),
863 String::from("a@b")
864 );
865 }
866
867 #[cfg(feature = "minidom")]
868 #[test]
869 fn minidom() {
870 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
871 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
872 assert_eq!(to, Jid::Full(FullJid::new("a", "b", "c")));
873
874 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
875 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
876 assert_eq!(to, Jid::Bare(BareJid::new("a", "b")));
877
878 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
879 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
880 assert_eq!(to, FullJid::new("a", "b", "c"));
881
882 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
883 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
884 assert_eq!(to, BareJid::new("a", "b"));
885 }
886
887 #[cfg(feature = "minidom")]
888 #[test]
889 fn minidom_into_attr() {
890 let full = FullJid::new("a", "b", "c");
891 let elem = minidom::Element::builder("message", "jabber:client")
892 .attr("from", full.clone())
893 .build();
894 assert_eq!(elem.attr("from"), Some(String::from(full).as_ref()));
895
896 let bare = BareJid::new("a", "b");
897 let elem = minidom::Element::builder("message", "jabber:client")
898 .attr("from", bare.clone())
899 .build();
900 assert_eq!(elem.attr("from"), Some(String::from(bare.clone()).as_ref()));
901
902 let jid = Jid::Bare(bare.clone());
903 let _elem = minidom::Element::builder("message", "jabber:client")
904 .attr("from", jid)
905 .build();
906 assert_eq!(elem.attr("from"), Some(String::from(bare).as_ref()));
907 }
908}