1// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
2// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
4// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
5// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
6//
7// This Source Code Form is subject to the terms of the Mozilla Public
8// License, v. 2.0. If a copy of the MPL was not distributed with this
9// file, You can obtain one at http://mozilla.org/MPL/2.0/.
10
11#![deny(missing_docs)]
12
13//! Provides a type for Jabber IDs.
14//!
15//! For usage, check the documentation on the `Jid` struct.
16
17use 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/// An error that signifies that a `Jid` cannot be parsed from a string.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum JidParseError {
28 /// Happens when there is no domain, that is either the string is empty,
29 /// starts with a /, or contains the @/ sequence.
30 NoDomain,
31
32 /// Happens when there is no resource, that is string contains no /.
33 NoResource,
34
35 /// Happens when the node is empty, that is the string starts with a @.
36 EmptyNode,
37
38 /// Happens when the resource is empty, that is the string ends with a /.
39 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/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61#[derive(Debug, Clone, PartialEq, Eq, Hash)]
62pub enum Jid {
63 /// Bare Jid
64 Bare(BareJid),
65
66 /// Full Jid
67 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 /// The node part of the Jabber ID, if it exists, else None.
118 pub fn node(self) -> Option<String> {
119 match self {
120 Jid::Bare(BareJid { node, .. }) | Jid::Full(FullJid { node, .. }) => node,
121 }
122 }
123
124 /// The domain of the Jabber ID.
125 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
152/// A struct representing a full Jabber ID.
153///
154/// A full Jabber ID is composed of 3 components, of which one is optional:
155///
156/// - A node/name, `node`, which is the optional part before the @.
157/// - A domain, `domain`, which is the mandatory part after the @ but before the /.
158/// - A resource, `resource`, which is the part after the /.
159///
160/// Unlike a `BareJid`, it always contains a resource, and should only be used when you are certain
161/// there is no case where a resource can be missing. Otherwise, use a `Jid` enum.
162#[derive(Clone, PartialEq, Eq, Hash)]
163pub struct FullJid {
164 /// The node part of the Jabber ID, if it exists, else None.
165 pub node: Option<String>,
166 /// The domain of the Jabber ID.
167 pub domain: String,
168 /// The resource of the Jabber ID.
169 pub resource: String,
170}
171
172/// A struct representing a bare Jabber ID.
173///
174/// A bare Jabber ID is composed of 2 components, of which one is optional:
175///
176/// - A node/name, `node`, which is the optional part before the @.
177/// - A domain, `domain`, which is the mandatory part after the @.
178///
179/// Unlike a `FullJid`, it can’t contain a resource, and should only be used when you are certain
180/// there is no case where a resource can be set. Otherwise, use a `Jid` enum.
181#[derive(Clone, PartialEq, Eq, Hash)]
182pub struct BareJid {
183 /// The node part of the Jabber ID, if it exists, else None.
184 pub node: Option<String>,
185 /// The domain of the Jabber ID.
186 pub domain: String,
187}
188
189impl From<FullJid> for String {
190 fn from(jid: FullJid) -> String {
191 String::from(&jid)
192 }
193}
194
195impl From<&FullJid> for String {
196 fn from(jid: &FullJid) -> String {
197 let mut string = String::new();
198 if let Some(ref node) = jid.node {
199 string.push_str(node);
200 string.push('@');
201 }
202 string.push_str(&jid.domain);
203 string.push('/');
204 string.push_str(&jid.resource);
205 string
206 }
207}
208
209impl From<BareJid> for String {
210 fn from(jid: BareJid) -> String {
211 String::from(&jid)
212 }
213}
214
215impl From<&BareJid> for String {
216 fn from(jid: &BareJid) -> String {
217 let mut string = String::new();
218 if let Some(ref node) = jid.node {
219 string.push_str(node);
220 string.push('@');
221 }
222 string.push_str(&jid.domain);
223 string
224 }
225}
226
227impl Into<BareJid> for FullJid {
228 fn into(self) -> BareJid {
229 BareJid {
230 node: self.node,
231 domain: self.domain,
232 }
233 }
234}
235
236impl fmt::Debug for FullJid {
237 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
238 write!(fmt, "FullJID({})", self)
239 }
240}
241
242impl fmt::Debug for BareJid {
243 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
244 write!(fmt, "BareJID({})", self)
245 }
246}
247
248impl fmt::Display for FullJid {
249 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
250 fmt.write_str(String::from(self.clone()).as_ref())
251 }
252}
253
254impl fmt::Display for BareJid {
255 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
256 fmt.write_str(String::from(self.clone()).as_ref())
257 }
258}
259
260#[cfg(feature = "serde")]
261impl Serialize for FullJid {
262 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
263 where
264 S: Serializer,
265 {
266 serializer.serialize_str(String::from(self).as_str())
267 }
268}
269
270#[cfg(feature = "serde")]
271impl Serialize for BareJid {
272 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
273 where
274 S: Serializer,
275 {
276 serializer.serialize_str(String::from(self).as_str())
277 }
278}
279
280enum ParserState {
281 Node,
282 Domain,
283 Resource,
284}
285
286type StringJid = (Option<String>, String, Option<String>);
287fn _from_str(s: &str) -> Result<StringJid, JidParseError> {
288 // TODO: very naive, may need to do it differently
289 let iter = s.chars();
290 let mut buf = String::with_capacity(s.len());
291 let mut state = ParserState::Node;
292 let mut node = None;
293 let mut domain = None;
294 let mut resource = None;
295 for c in iter {
296 match state {
297 ParserState::Node => {
298 match c {
299 '@' => {
300 if buf == "" {
301 return Err(JidParseError::EmptyNode);
302 }
303 state = ParserState::Domain;
304 node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
305 buf.clear();
306 }
307 '/' => {
308 if buf == "" {
309 return Err(JidParseError::NoDomain);
310 }
311 state = ParserState::Resource;
312 domain = Some(buf.clone()); // TODO: performance tweaks
313 buf.clear();
314 }
315 c => {
316 buf.push(c);
317 }
318 }
319 }
320 ParserState::Domain => {
321 match c {
322 '/' => {
323 if buf == "" {
324 return Err(JidParseError::NoDomain);
325 }
326 state = ParserState::Resource;
327 domain = Some(buf.clone()); // TODO: performance tweaks
328 buf.clear();
329 }
330 c => {
331 buf.push(c);
332 }
333 }
334 }
335 ParserState::Resource => {
336 buf.push(c);
337 }
338 }
339 }
340 if !buf.is_empty() {
341 match state {
342 ParserState::Node => {
343 domain = Some(buf);
344 }
345 ParserState::Domain => {
346 domain = Some(buf);
347 }
348 ParserState::Resource => {
349 resource = Some(buf);
350 }
351 }
352 } else if let ParserState::Resource = state {
353 return Err(JidParseError::EmptyResource);
354 }
355 Ok((node, domain.ok_or(JidParseError::NoDomain)?, resource))
356}
357
358impl FromStr for FullJid {
359 type Err = JidParseError;
360
361 fn from_str(s: &str) -> Result<FullJid, JidParseError> {
362 let (ns, ds, rs): StringJid = _from_str(s)?;
363 Ok(FullJid {
364 node: ns,
365 domain: ds,
366 resource: rs.ok_or(JidParseError::NoResource)?,
367 })
368 }
369}
370
371#[cfg(feature = "serde")]
372impl<'de> Deserialize<'de> for FullJid {
373 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
374 where
375 D: Deserializer<'de>,
376 {
377 let s = String::deserialize(deserializer)?;
378 FullJid::from_str(&s).map_err(de::Error::custom)
379 }
380}
381
382#[cfg(feature = "serde")]
383impl<'de> Deserialize<'de> for BareJid {
384 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
385 where
386 D: Deserializer<'de>,
387 {
388 let s = String::deserialize(deserializer)?;
389 BareJid::from_str(&s).map_err(de::Error::custom)
390 }
391}
392
393impl FullJid {
394 /// Constructs a full Jabber ID containing all three components.
395 ///
396 /// This is of the form `node`@`domain`/`resource`.
397 ///
398 /// # Examples
399 ///
400 /// ```
401 /// use jid::FullJid;
402 ///
403 /// let jid = FullJid::new("node", "domain", "resource");
404 ///
405 /// assert_eq!(jid.node, Some("node".to_owned()));
406 /// assert_eq!(jid.domain, "domain".to_owned());
407 /// assert_eq!(jid.resource, "resource".to_owned());
408 /// ```
409 pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
410 where
411 NS: Into<String>,
412 DS: Into<String>,
413 RS: Into<String>,
414 {
415 FullJid {
416 node: Some(node.into()),
417 domain: domain.into(),
418 resource: resource.into(),
419 }
420 }
421
422 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
423 ///
424 /// # Examples
425 ///
426 /// ```
427 /// use jid::FullJid;
428 ///
429 /// let jid = FullJid::new("node", "domain", "resource");
430 ///
431 /// assert_eq!(jid.node, Some("node".to_owned()));
432 ///
433 /// let new_jid = jid.with_node("new_node");
434 ///
435 /// assert_eq!(new_jid.node, Some("new_node".to_owned()));
436 /// ```
437 pub fn with_node<NS>(&self, node: NS) -> FullJid
438 where
439 NS: Into<String>,
440 {
441 FullJid {
442 node: Some(node.into()),
443 domain: self.domain.clone(),
444 resource: self.resource.clone(),
445 }
446 }
447
448 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
449 ///
450 /// # Examples
451 ///
452 /// ```
453 /// use jid::FullJid;
454 ///
455 /// let jid = FullJid::new("node", "domain", "resource");
456 ///
457 /// assert_eq!(jid.domain, "domain".to_owned());
458 ///
459 /// let new_jid = jid.with_domain("new_domain");
460 ///
461 /// assert_eq!(new_jid.domain, "new_domain");
462 /// ```
463 pub fn with_domain<DS>(&self, domain: DS) -> FullJid
464 where
465 DS: Into<String>,
466 {
467 FullJid {
468 node: self.node.clone(),
469 domain: domain.into(),
470 resource: self.resource.clone(),
471 }
472 }
473
474 /// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
475 ///
476 /// # Examples
477 ///
478 /// ```
479 /// use jid::FullJid;
480 ///
481 /// let jid = FullJid::new("node", "domain", "resource");
482 ///
483 /// assert_eq!(jid.resource, "resource".to_owned());
484 ///
485 /// let new_jid = jid.with_resource("new_resource");
486 ///
487 /// assert_eq!(new_jid.resource, "new_resource");
488 /// ```
489 pub fn with_resource<RS>(&self, resource: RS) -> FullJid
490 where
491 RS: Into<String>,
492 {
493 FullJid {
494 node: self.node.clone(),
495 domain: self.domain.clone(),
496 resource: resource.into(),
497 }
498 }
499}
500
501impl FromStr for BareJid {
502 type Err = JidParseError;
503
504 fn from_str(s: &str) -> Result<BareJid, JidParseError> {
505 let (ns, ds, _rs): StringJid = _from_str(s)?;
506 Ok(BareJid {
507 node: ns,
508 domain: ds,
509 })
510 }
511}
512
513impl BareJid {
514 /// Constructs a bare Jabber ID, containing two components.
515 ///
516 /// This is of the form `node`@`domain`.
517 ///
518 /// # Examples
519 ///
520 /// ```
521 /// use jid::BareJid;
522 ///
523 /// let jid = BareJid::new("node", "domain");
524 ///
525 /// assert_eq!(jid.node, Some("node".to_owned()));
526 /// assert_eq!(jid.domain, "domain".to_owned());
527 /// ```
528 pub fn new<NS, DS>(node: NS, domain: DS) -> BareJid
529 where
530 NS: Into<String>,
531 DS: Into<String>,
532 {
533 BareJid {
534 node: Some(node.into()),
535 domain: domain.into(),
536 }
537 }
538
539 /// Constructs a bare Jabber ID containing only a `domain`.
540 ///
541 /// This is of the form `domain`.
542 ///
543 /// # Examples
544 ///
545 /// ```
546 /// use jid::BareJid;
547 ///
548 /// let jid = BareJid::domain("domain");
549 ///
550 /// assert_eq!(jid.node, None);
551 /// assert_eq!(jid.domain, "domain".to_owned());
552 /// ```
553 pub fn domain<DS>(domain: DS) -> BareJid
554 where
555 DS: Into<String>,
556 {
557 BareJid {
558 node: None,
559 domain: domain.into(),
560 }
561 }
562
563 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
564 ///
565 /// # Examples
566 ///
567 /// ```
568 /// use jid::BareJid;
569 ///
570 /// let jid = BareJid::domain("domain");
571 ///
572 /// assert_eq!(jid.node, None);
573 ///
574 /// let new_jid = jid.with_node("node");
575 ///
576 /// assert_eq!(new_jid.node, Some("node".to_owned()));
577 /// ```
578 pub fn with_node<NS>(&self, node: NS) -> BareJid
579 where
580 NS: Into<String>,
581 {
582 BareJid {
583 node: Some(node.into()),
584 domain: self.domain.clone(),
585 }
586 }
587
588 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
589 ///
590 /// # Examples
591 ///
592 /// ```
593 /// use jid::BareJid;
594 ///
595 /// let jid = BareJid::domain("domain");
596 ///
597 /// assert_eq!(jid.domain, "domain");
598 ///
599 /// let new_jid = jid.with_domain("new_domain");
600 ///
601 /// assert_eq!(new_jid.domain, "new_domain");
602 /// ```
603 pub fn with_domain<DS>(&self, domain: DS) -> BareJid
604 where
605 DS: Into<String>,
606 {
607 BareJid {
608 node: self.node.clone(),
609 domain: domain.into(),
610 }
611 }
612
613 /// Constructs a full Jabber ID from a bare Jabber ID, specifying a `resource`.
614 ///
615 /// # Examples
616 ///
617 /// ```
618 /// use jid::BareJid;
619 ///
620 /// let bare = BareJid::new("node", "domain");
621 /// let full = bare.with_resource("resource");
622 ///
623 /// assert_eq!(full.node, Some("node".to_owned()));
624 /// assert_eq!(full.domain, "domain".to_owned());
625 /// assert_eq!(full.resource, "resource".to_owned());
626 /// ```
627 pub fn with_resource<RS>(self, resource: RS) -> FullJid
628 where
629 RS: Into<String>,
630 {
631 FullJid {
632 node: self.node,
633 domain: self.domain,
634 resource: resource.into(),
635 }
636 }
637}
638
639#[cfg(feature = "minidom")]
640use minidom::{IntoAttributeValue, Node};
641
642#[cfg(feature = "minidom")]
643impl IntoAttributeValue for Jid {
644 fn into_attribute_value(self) -> Option<String> {
645 Some(String::from(self))
646 }
647}
648
649#[cfg(feature = "minidom")]
650impl Into<Node> for Jid {
651 fn into(self) -> Node {
652 Node::Text(String::from(self))
653 }
654}
655
656#[cfg(feature = "minidom")]
657impl IntoAttributeValue for FullJid {
658 fn into_attribute_value(self) -> Option<String> {
659 Some(String::from(self))
660 }
661}
662
663#[cfg(feature = "minidom")]
664impl Into<Node> for FullJid {
665 fn into(self) -> Node {
666 Node::Text(String::from(self))
667 }
668}
669
670#[cfg(feature = "minidom")]
671impl IntoAttributeValue for BareJid {
672 fn into_attribute_value(self) -> Option<String> {
673 Some(String::from(self))
674 }
675}
676
677#[cfg(feature = "minidom")]
678impl Into<Node> for BareJid {
679 fn into(self) -> Node {
680 Node::Text(String::from(self))
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 use std::collections::HashMap;
689 use std::str::FromStr;
690
691 #[test]
692 fn can_parse_full_jids() {
693 assert_eq!(
694 FullJid::from_str("a@b.c/d"),
695 Ok(FullJid::new("a", "b.c", "d"))
696 );
697 assert_eq!(
698 FullJid::from_str("b.c/d"),
699 Ok(FullJid {
700 node: None,
701 domain: "b.c".to_owned(),
702 resource: "d".to_owned(),
703 })
704 );
705
706 assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
707 assert_eq!(FullJid::from_str("b.c"), Err(JidParseError::NoResource));
708 }
709
710 #[test]
711 fn can_parse_bare_jids() {
712 assert_eq!(BareJid::from_str("a@b.c/d"), Ok(BareJid::new("a", "b.c")));
713 assert_eq!(
714 BareJid::from_str("b.c/d"),
715 Ok(BareJid {
716 node: None,
717 domain: "b.c".to_owned(),
718 })
719 );
720
721 assert_eq!(BareJid::from_str("a@b.c"), Ok(BareJid::new("a", "b.c")));
722 assert_eq!(
723 BareJid::from_str("b.c"),
724 Ok(BareJid {
725 node: None,
726 domain: "b.c".to_owned(),
727 })
728 );
729 }
730
731 #[test]
732 fn can_parse_jids() {
733 let full = FullJid::from_str("a@b.c/d").unwrap();
734 let bare = BareJid::from_str("e@f.g").unwrap();
735
736 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
737 assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
738 }
739
740 #[test]
741 fn full_to_bare_jid() {
742 let bare: BareJid = FullJid::new("a", "b.c", "d").into();
743 assert_eq!(bare, BareJid::new("a", "b.c"));
744 }
745
746 #[test]
747 fn bare_to_full_jid() {
748 assert_eq!(
749 BareJid::new("a", "b.c").with_resource("d"),
750 FullJid::new("a", "b.c", "d")
751 );
752 }
753
754 #[test]
755 fn node_from_jid() {
756 assert_eq!(
757 Jid::Full(FullJid::new("a", "b.c", "d")).node(),
758 Some(String::from("a")),
759 );
760 }
761
762 #[test]
763 fn domain_from_jid() {
764 assert_eq!(
765 Jid::Bare(BareJid::new("a", "b.c")).domain(),
766 String::from("b.c"),
767 );
768 }
769
770 #[test]
771 fn jid_to_full_bare() {
772 let full = FullJid::new("a", "b.c", "d");
773 let bare = BareJid::new("a", "b.c");
774
775 assert_eq!(FullJid::try_from(Jid::Full(full.clone())), Ok(full.clone()),);
776 assert_eq!(
777 FullJid::try_from(Jid::Bare(bare.clone())),
778 Err(JidParseError::NoResource),
779 );
780 assert_eq!(BareJid::from(Jid::Full(full.clone())), bare.clone(),);
781 assert_eq!(BareJid::from(Jid::Bare(bare.clone())), bare,);
782 }
783
784 #[test]
785 fn serialise() {
786 assert_eq!(
787 String::from(FullJid::new("a", "b", "c")),
788 String::from("a@b/c")
789 );
790 assert_eq!(String::from(BareJid::new("a", "b")), String::from("a@b"));
791 }
792
793 #[test]
794 fn hash() {
795 let _map: HashMap<Jid, String> = HashMap::new();
796 }
797
798 #[test]
799 fn invalid_jids() {
800 assert_eq!(BareJid::from_str(""), Err(JidParseError::NoDomain));
801 assert_eq!(BareJid::from_str("/c"), Err(JidParseError::NoDomain));
802 assert_eq!(BareJid::from_str("a@/c"), Err(JidParseError::NoDomain));
803 assert_eq!(BareJid::from_str("@b"), Err(JidParseError::EmptyNode));
804 assert_eq!(BareJid::from_str("b/"), Err(JidParseError::EmptyResource));
805
806 assert_eq!(FullJid::from_str(""), Err(JidParseError::NoDomain));
807 assert_eq!(FullJid::from_str("/c"), Err(JidParseError::NoDomain));
808 assert_eq!(FullJid::from_str("a@/c"), Err(JidParseError::NoDomain));
809 assert_eq!(FullJid::from_str("@b"), Err(JidParseError::EmptyNode));
810 assert_eq!(FullJid::from_str("b/"), Err(JidParseError::EmptyResource));
811 assert_eq!(FullJid::from_str("a@b"), Err(JidParseError::NoResource));
812 }
813
814 #[test]
815 fn display_jids() {
816 assert_eq!(
817 format!("{}", FullJid::new("a", "b", "c")),
818 String::from("a@b/c")
819 );
820 assert_eq!(format!("{}", BareJid::new("a", "b")), String::from("a@b"));
821 assert_eq!(
822 format!("{}", Jid::Full(FullJid::new("a", "b", "c"))),
823 String::from("a@b/c")
824 );
825 assert_eq!(
826 format!("{}", Jid::Bare(BareJid::new("a", "b"))),
827 String::from("a@b")
828 );
829 }
830
831 #[cfg(feature = "minidom")]
832 #[test]
833 fn minidom() {
834 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
835 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
836 assert_eq!(to, Jid::Full(FullJid::new("a", "b", "c")));
837
838 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
839 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
840 assert_eq!(to, Jid::Bare(BareJid::new("a", "b")));
841
842 let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
843 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
844 assert_eq!(to, FullJid::new("a", "b", "c"));
845
846 let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
847 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
848 assert_eq!(to, BareJid::new("a", "b"));
849 }
850
851 #[cfg(feature = "minidom")]
852 #[test]
853 fn minidom_into_attr() {
854 let full = FullJid::new("a", "b", "c");
855 let elem = minidom::Element::builder("message", "jabber:client")
856 .attr("from", full.clone())
857 .build();
858 assert_eq!(elem.attr("from"), Some(String::from(full).as_ref()));
859
860 let bare = BareJid::new("a", "b");
861 let elem = minidom::Element::builder("message", "jabber:client")
862 .attr("from", bare.clone())
863 .build();
864 assert_eq!(elem.attr("from"), Some(String::from(bare.clone()).as_ref()));
865
866 let jid = Jid::Bare(bare.clone());
867 let _elem = minidom::Element::builder("message", "jabber:client")
868 .attr("from", jid)
869 .build();
870 assert_eq!(elem.attr("from"), Some(String::from(bare).as_ref()));
871 }
872}