1#![deny(missing_docs)]
2
3//! Provides a type for Jabber IDs.
4//!
5//! For usage, check the documentation on the `Jid` struct.
6
7#[macro_use]
8extern crate failure_derive;
9
10use std::convert::Into;
11use std::fmt;
12use std::str::FromStr;
13
14/// An error that signifies that a `Jid` cannot be parsed from a string.
15#[derive(Debug, Clone, PartialEq, Eq, Fail)]
16pub enum JidParseError {
17 /// Happens when there is no domain, that is either the string is empty,
18 /// starts with a /, or contains the @/ sequence.
19 #[fail(display = "no domain found in this JID")]
20 NoDomain,
21
22 /// Happens when there is no resource, that is string contains no /.
23 #[fail(display = "no resource found in this JID")]
24 NoResource,
25
26 /// Happens when the node is empty, that is the string starts with a @.
27 #[fail(display = "nodepart empty despite the presence of a @")]
28 EmptyNode,
29
30 /// Happens when the resource is empty, that is the string ends with a /.
31 #[fail(display = "resource empty despite the presence of a /")]
32 EmptyResource,
33}
34
35/// An enum representing a Jabber ID. It can be either a `FullJid` or a `BareJid`.
36#[derive(Debug, Clone, PartialEq)]
37pub enum Jid {
38 /// Bare Jid
39 Bare(BareJid),
40
41 /// Full Jid
42 Full(FullJid),
43}
44
45impl FromStr for Jid {
46 type Err = JidParseError;
47
48 fn from_str(s: &str) -> Result<Self, Self::Err> {
49 let (ns, ds, rs): StringJid = _from_str(s)?;
50 Ok(match rs {
51 Some(rs) => Jid::Full(FullJid {
52 node: ns,
53 domain: ds,
54 resource: rs,
55 }),
56 None => Jid::Bare(BareJid {
57 node: ns,
58 domain: ds,
59 })
60 })
61 }
62}
63
64impl From<Jid> for String {
65 fn from(jid: Jid) -> String {
66 match jid {
67 Jid::Bare(bare) => String::from(bare),
68 Jid::Full(full) => String::from(full),
69 }
70 }
71}
72
73/// A struct representing a Full Jabber ID.
74///
75/// A Full Jabber ID is composed of 3 components, of which one is optional:
76///
77/// - A node/name, `node`, which is the optional part before the @.
78/// - A domain, `domain`, which is the mandatory part after the @ but before the /.
79/// - A resource, `resource`, which is the part after the /.
80#[derive(Clone, PartialEq, Eq, Hash)]
81pub struct FullJid {
82 /// The node part of the Jabber ID, if it exists, else None.
83 pub node: Option<String>,
84 /// The domain of the Jabber ID.
85 pub domain: String,
86 /// The resource of the Jabber ID.
87 pub resource: String,
88}
89
90/// A struct representing a Bare Jabber ID.
91///
92/// A Bare Jabber ID is composed of 2 components, of which one is optional:
93///
94/// - A node/name, `node`, which is the optional part before the @.
95/// - A domain, `domain`, which is the mandatory part after the @ but before the /.
96#[derive(Clone, PartialEq, Eq, Hash)]
97pub struct BareJid {
98 /// The node part of the Jabber ID, if it exists, else None.
99 pub node: Option<String>,
100 /// The domain of the Jabber ID.
101 pub domain: String,
102}
103
104impl From<FullJid> for String {
105 fn from(jid: FullJid) -> String {
106 let mut string = String::new();
107 if let Some(ref node) = jid.node {
108 string.push_str(node);
109 string.push('@');
110 }
111 string.push_str(&jid.domain);
112 string.push('/');
113 string.push_str(&jid.resource);
114 string
115 }
116}
117
118impl From<BareJid> for String {
119 fn from(jid: BareJid) -> String {
120 let mut string = String::new();
121 if let Some(ref node) = jid.node {
122 string.push_str(node);
123 string.push('@');
124 }
125 string.push_str(&jid.domain);
126 string
127 }
128}
129
130impl Into<BareJid> for FullJid {
131 fn into(self) -> BareJid {
132 BareJid {
133 node: self.node,
134 domain: self.domain,
135 }
136 }
137}
138
139impl fmt::Debug for FullJid {
140 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
141 write!(fmt, "FullJID({})", self)
142 }
143}
144
145impl fmt::Debug for BareJid {
146 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
147 write!(fmt, "BareJID({})", self)
148 }
149}
150
151impl fmt::Display for FullJid {
152 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
153 fmt.write_str(String::from(self.clone()).as_ref())
154 }
155}
156
157impl fmt::Display for BareJid {
158 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
159 fmt.write_str(String::from(self.clone()).as_ref())
160 }
161}
162
163enum ParserState {
164 Node,
165 Domain,
166 Resource,
167}
168
169type StringJid = (Option<String>, String, Option<String>);
170fn _from_str(s: &str) -> Result<StringJid, JidParseError> {
171 // TODO: very naive, may need to do it differently
172 let iter = s.chars();
173 let mut buf = String::with_capacity(s.len());
174 let mut state = ParserState::Node;
175 let mut node = None;
176 let mut domain = None;
177 let mut resource = None;
178 for c in iter {
179 match state {
180 ParserState::Node => {
181 match c {
182 '@' => {
183 if buf == "" {
184 return Err(JidParseError::EmptyNode);
185 }
186 state = ParserState::Domain;
187 node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
188 buf.clear();
189 }
190 '/' => {
191 if buf == "" {
192 return Err(JidParseError::NoDomain);
193 }
194 state = ParserState::Resource;
195 domain = Some(buf.clone()); // TODO: performance tweaks
196 buf.clear();
197 }
198 c => {
199 buf.push(c);
200 }
201 }
202 }
203 ParserState::Domain => {
204 match c {
205 '/' => {
206 if buf == "" {
207 return Err(JidParseError::NoDomain);
208 }
209 state = ParserState::Resource;
210 domain = Some(buf.clone()); // TODO: performance tweaks
211 buf.clear();
212 }
213 c => {
214 buf.push(c);
215 }
216 }
217 }
218 ParserState::Resource => {
219 buf.push(c);
220 }
221 }
222 }
223 if !buf.is_empty() {
224 match state {
225 ParserState::Node => {
226 domain = Some(buf);
227 }
228 ParserState::Domain => {
229 domain = Some(buf);
230 }
231 ParserState::Resource => {
232 resource = Some(buf);
233 }
234 }
235 } else if let ParserState::Resource = state {
236 return Err(JidParseError::EmptyResource);
237 }
238 Ok((
239 node,
240 domain.ok_or(JidParseError::NoDomain)?,
241 resource,
242 ))
243}
244
245impl FromStr for FullJid {
246 type Err = JidParseError;
247
248 fn from_str(s: &str) -> Result<FullJid, JidParseError> {
249 let (ns, ds, rs): StringJid = _from_str(s)?;
250 Ok(FullJid {
251 node: ns,
252 domain: ds,
253 resource: rs.ok_or(JidParseError::NoResource)?,
254 })
255 }
256}
257
258impl FullJid {
259 /// Constructs a Full Jabber ID containing all three components.
260 ///
261 /// This is of the form `node`@`domain`/`resource`.
262 ///
263 /// # Examples
264 ///
265 /// ```
266 /// use jid::FullJid;
267 ///
268 /// let jid = FullJid::new("node", "domain", "resource");
269 ///
270 /// assert_eq!(jid.node, Some("node".to_owned()));
271 /// assert_eq!(jid.domain, "domain".to_owned());
272 /// assert_eq!(jid.resource, "resource".to_owned());
273 /// ```
274 pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
275 where
276 NS: Into<String>,
277 DS: Into<String>,
278 RS: Into<String>,
279 {
280 FullJid {
281 node: Some(node.into()),
282 domain: domain.into(),
283 resource: resource.into()
284 }
285 }
286
287 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
288 ///
289 /// # Examples
290 ///
291 /// ```
292 /// use jid::FullJid;
293 ///
294 /// let jid = FullJid::new("node", "domain", "resource");
295 ///
296 /// assert_eq!(jid.node, Some("node".to_owned()));
297 ///
298 /// let new_jid = jid.with_node("new_node");
299 ///
300 /// assert_eq!(new_jid.node, Some("new_node".to_owned()));
301 /// ```
302 pub fn with_node<NS>(&self, node: NS) -> FullJid
303 where
304 NS: Into<String>,
305 {
306 FullJid {
307 node: Some(node.into()),
308 domain: self.domain.clone(),
309 resource: self.resource.clone(),
310 }
311 }
312
313 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
314 ///
315 /// # Examples
316 ///
317 /// ```
318 /// use jid::FullJid;
319 ///
320 /// let jid = FullJid::new("node", "domain", "resource");
321 ///
322 /// assert_eq!(jid.domain, "domain".to_owned());
323 ///
324 /// let new_jid = jid.with_domain("new_domain");
325 ///
326 /// assert_eq!(new_jid.domain, "new_domain");
327 /// ```
328 pub fn with_domain<DS>(&self, domain: DS) -> FullJid
329 where
330 DS: Into<String>,
331 {
332 FullJid {
333 node: self.node.clone(),
334 domain: domain.into(),
335 resource: self.resource.clone(),
336 }
337 }
338
339 /// Constructs a Full Jabber ID from a Bare Jabber ID, specifying a `resource`.
340 ///
341 /// # Examples
342 ///
343 /// ```
344 /// use jid::FullJid;
345 ///
346 /// let jid = FullJid::new("node", "domain", "resource");
347 ///
348 /// assert_eq!(jid.resource, "resource".to_owned());
349 ///
350 /// let new_jid = jid.with_resource("new_resource");
351 ///
352 /// assert_eq!(new_jid.resource, "new_resource");
353 /// ```
354 pub fn with_resource<RS>(&self, resource: RS) -> FullJid
355 where
356 RS: Into<String>,
357 {
358 FullJid {
359 node: self.node.clone(),
360 domain: self.domain.clone(),
361 resource: resource.into(),
362 }
363 }
364}
365
366impl FromStr for BareJid {
367 type Err = JidParseError;
368
369 fn from_str(s: &str) -> Result<BareJid, JidParseError> {
370 let (ns, ds, _rs): StringJid = _from_str(s)?;
371 Ok(BareJid {
372 node: ns,
373 domain: ds,
374 })
375 }
376}
377
378impl BareJid {
379 /// Constructs a Bare Jabber ID, containing two components.
380 ///
381 /// This is of the form `node`@`domain`.
382 ///
383 /// # Examples
384 ///
385 /// ```
386 /// use jid::BareJid;
387 ///
388 /// let jid = BareJid::new("node", "domain");
389 ///
390 /// assert_eq!(jid.node, Some("node".to_owned()));
391 /// assert_eq!(jid.domain, "domain".to_owned());
392 /// ```
393 pub fn new<NS, DS>(node: NS, domain: DS) -> BareJid
394 where
395 NS: Into<String>,
396 DS: Into<String>,
397 {
398 BareJid {
399 node: Some(node.into()),
400 domain: domain.into(),
401 }
402 }
403
404 /// Constructs a Bare Jabber ID containing only a `domain`.
405 ///
406 /// This is of the form `domain`.
407 ///
408 /// # Examples
409 ///
410 /// ```
411 /// use jid::BareJid;
412 ///
413 /// let jid = BareJid::domain("domain");
414 ///
415 /// assert_eq!(jid.node, None);
416 /// assert_eq!(jid.domain, "domain".to_owned());
417 /// ```
418 pub fn domain<DS>(domain: DS) -> BareJid
419 where
420 DS: Into<String>,
421 {
422 BareJid {
423 node: None,
424 domain: domain.into(),
425 }
426 }
427
428 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
429 ///
430 /// # Examples
431 ///
432 /// ```
433 /// use jid::BareJid;
434 ///
435 /// let jid = BareJid::domain("domain");
436 ///
437 /// assert_eq!(jid.node, None);
438 ///
439 /// let new_jid = jid.with_node("node");
440 ///
441 /// assert_eq!(new_jid.node, Some("node".to_owned()));
442 /// ```
443 pub fn with_node<NS>(&self, node: NS) -> BareJid
444 where
445 NS: Into<String>,
446 {
447 BareJid {
448 node: Some(node.into()),
449 domain: self.domain.clone(),
450 }
451 }
452
453 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
454 ///
455 /// # Examples
456 ///
457 /// ```
458 /// use jid::BareJid;
459 ///
460 /// let jid = BareJid::domain("domain");
461 ///
462 /// assert_eq!(jid.domain, "domain");
463 ///
464 /// let new_jid = jid.with_domain("new_domain");
465 ///
466 /// assert_eq!(new_jid.domain, "new_domain");
467 /// ```
468 pub fn with_domain<DS>(&self, domain: DS) -> BareJid
469 where
470 DS: Into<String>,
471 {
472 BareJid {
473 node: self.node.clone(),
474 domain: domain.into(),
475 }
476 }
477
478 /// Constructs a Full Jabber ID from a Bare Jabber ID, specifying a `resource`.
479 ///
480 /// # Examples
481 ///
482 /// ```
483 /// use jid::BareJid;
484 ///
485 /// let bare = BareJid::new("node", "domain");
486 /// let full = bare.with_resource("resource");
487 ///
488 /// assert_eq!(full.node, Some("node".to_owned()));
489 /// assert_eq!(full.domain, "domain".to_owned());
490 /// assert_eq!(full.resource, "resource".to_owned());
491 /// ```
492 pub fn with_resource<RS>(self, resource: RS) -> FullJid
493 where
494 RS: Into<String>,
495 {
496 FullJid {
497 node: self.node,
498 domain: self.domain,
499 resource: resource.into(),
500 }
501 }
502}
503
504#[cfg(feature = "minidom")]
505use minidom::{ElementEmitter, IntoAttributeValue, IntoElements};
506
507#[cfg(feature = "minidom")]
508impl IntoAttributeValue for Jid {
509 fn into_attribute_value(self) -> Option<String> {
510 Some(String::from(self))
511 }
512}
513
514#[cfg(feature = "minidom")]
515impl IntoElements for Jid {
516 fn into_elements(self, emitter: &mut ElementEmitter) {
517 emitter.append_text_node(String::from(self))
518 }
519}
520
521#[cfg(feature = "minidom")]
522impl IntoAttributeValue for FullJid {
523 fn into_attribute_value(self) -> Option<String> {
524 Some(String::from(self))
525 }
526}
527
528#[cfg(feature = "minidom")]
529impl IntoElements for FullJid {
530 fn into_elements(self, emitter: &mut ElementEmitter) {
531 emitter.append_text_node(String::from(self))
532 }
533}
534
535#[cfg(feature = "minidom")]
536impl IntoAttributeValue for BareJid {
537 fn into_attribute_value(self) -> Option<String> {
538 Some(String::from(self))
539 }
540}
541
542#[cfg(feature = "minidom")]
543impl IntoElements for BareJid {
544 fn into_elements(self, emitter: &mut ElementEmitter) {
545 emitter.append_text_node(String::from(self))
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 use std::str::FromStr;
554
555 #[test]
556 fn can_parse_full_jids() {
557 assert_eq!(FullJid::from_str("a@b.c/d"), Ok(FullJid::new("a", "b.c", "d")));
558 assert_eq!(
559 FullJid::from_str("b.c/d"),
560 Ok(FullJid {
561 node: None,
562 domain: "b.c".to_owned(),
563 resource: "d".to_owned(),
564 })
565 );
566
567 assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
568 assert_eq!(FullJid::from_str("b.c"), Err(JidParseError::NoResource));
569 }
570
571 #[test]
572 fn can_parse_bare_jids() {
573 assert_eq!(BareJid::from_str("a@b.c/d"), Ok(BareJid::new("a", "b.c")));
574 assert_eq!(
575 BareJid::from_str("b.c/d"),
576 Ok(BareJid {
577 node: None,
578 domain: "b.c".to_owned(),
579 })
580 );
581
582 assert_eq!(BareJid::from_str("a@b.c"), Ok(BareJid::new("a", "b.c")));
583 assert_eq!(
584 BareJid::from_str("b.c"),
585 Ok(BareJid {
586 node: None,
587 domain: "b.c".to_owned(),
588 })
589 );
590 }
591
592 #[test]
593 fn can_parse_jids() {
594 let full = FullJid::from_str("a@b.c/d").unwrap();
595 let bare = BareJid::from_str("e@f.g").unwrap();
596
597 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
598 assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
599 }
600
601 #[test]
602 fn full_to_bare_jid() {
603 let bare: BareJid = FullJid::new("a", "b.c", "d").into();
604 assert_eq!(bare, BareJid::new("a", "b.c"));
605 }
606
607 #[test]
608 fn bare_to_full_jid() {
609 assert_eq!(BareJid::new("a", "b.c").with_resource("d"), FullJid::new("a", "b.c", "d"));
610 }
611
612 #[test]
613 fn serialise() {
614 assert_eq!(
615 String::from(FullJid::new("a", "b", "c")),
616 String::from("a@b/c")
617 );
618 assert_eq!(
619 String::from(BareJid::new("a", "b")),
620 String::from("a@b")
621 );
622 }
623
624 #[test]
625 fn invalid_jids() {
626 assert_eq!(BareJid::from_str(""), Err(JidParseError::NoDomain));
627 assert_eq!(BareJid::from_str("/c"), Err(JidParseError::NoDomain));
628 assert_eq!(BareJid::from_str("a@/c"), Err(JidParseError::NoDomain));
629 assert_eq!(BareJid::from_str("@b"), Err(JidParseError::EmptyNode));
630 assert_eq!(BareJid::from_str("b/"), Err(JidParseError::EmptyResource));
631
632 assert_eq!(FullJid::from_str(""), Err(JidParseError::NoDomain));
633 assert_eq!(FullJid::from_str("/c"), Err(JidParseError::NoDomain));
634 assert_eq!(FullJid::from_str("a@/c"), Err(JidParseError::NoDomain));
635 assert_eq!(FullJid::from_str("@b"), Err(JidParseError::EmptyNode));
636 assert_eq!(FullJid::from_str("b/"), Err(JidParseError::EmptyResource));
637 assert_eq!(FullJid::from_str("a@b"), Err(JidParseError::NoResource));
638 }
639
640 #[cfg(feature = "minidom")]
641 #[test]
642 fn minidom() {
643 let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
644 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
645 assert_eq!(to, Jid::Full(FullJid::new("a", "b", "c")));
646
647 let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
648 let to: Jid = elem.attr("from").unwrap().parse().unwrap();
649 assert_eq!(to, Jid::Bare(BareJid::new("a", "b")));
650
651 let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
652 let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
653 assert_eq!(to, FullJid::new("a", "b", "c"));
654
655 let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
656 let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
657 assert_eq!(to, BareJid::new("a", "b"));
658 }
659
660 #[cfg(feature = "minidom")]
661 #[test]
662 fn minidom_into_attr() {
663 let full = FullJid::new("a", "b", "c");
664 let elem = minidom::Element::builder("message")
665 .ns("jabber:client")
666 .attr("from", full.clone())
667 .build();
668 assert_eq!(elem.attr("from"), Some(String::from(full).as_ref()));
669
670 let bare = BareJid::new("a", "b");
671 let elem = minidom::Element::builder("message")
672 .ns("jabber:client")
673 .attr("from", bare.clone())
674 .build();
675 assert_eq!(elem.attr("from"), Some(String::from(bare.clone()).as_ref()));
676
677 let jid = Jid::Bare(bare.clone());
678 let _elem = minidom::Element::builder("message")
679 .ns("jabber:client")
680 .attr("from", jid)
681 .build();
682 assert_eq!(elem.attr("from"), Some(String::from(bare).as_ref()));
683 }
684}