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