lib.rs

  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((node, domain.ok_or(JidParseError::NoDomain)?, resource))
239}
240
241impl FromStr for FullJid {
242    type Err = JidParseError;
243
244    fn from_str(s: &str) -> Result<FullJid, JidParseError> {
245        let (ns, ds, rs): StringJid = _from_str(s)?;
246        Ok(FullJid {
247            node: ns,
248            domain: ds,
249            resource: rs.ok_or(JidParseError::NoResource)?,
250        })
251    }
252}
253
254impl FullJid {
255    /// Constructs a Full Jabber ID containing all three components.
256    ///
257    /// This is of the form `node`@`domain`/`resource`.
258    ///
259    /// # Examples
260    ///
261    /// ```
262    /// use jid::FullJid;
263    ///
264    /// let jid = FullJid::new("node", "domain", "resource");
265    ///
266    /// assert_eq!(jid.node, Some("node".to_owned()));
267    /// assert_eq!(jid.domain, "domain".to_owned());
268    /// assert_eq!(jid.resource, "resource".to_owned());
269    /// ```
270    pub fn new<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> FullJid
271    where
272        NS: Into<String>,
273        DS: Into<String>,
274        RS: Into<String>,
275    {
276        FullJid {
277            node: Some(node.into()),
278            domain: domain.into(),
279            resource: resource.into(),
280        }
281    }
282
283    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
284    ///
285    /// # Examples
286    ///
287    /// ```
288    /// use jid::FullJid;
289    ///
290    /// let jid = FullJid::new("node", "domain", "resource");
291    ///
292    /// assert_eq!(jid.node, Some("node".to_owned()));
293    ///
294    /// let new_jid = jid.with_node("new_node");
295    ///
296    /// assert_eq!(new_jid.node, Some("new_node".to_owned()));
297    /// ```
298    pub fn with_node<NS>(&self, node: NS) -> FullJid
299    where
300        NS: Into<String>,
301    {
302        FullJid {
303            node: Some(node.into()),
304            domain: self.domain.clone(),
305            resource: self.resource.clone(),
306        }
307    }
308
309    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use jid::FullJid;
315    ///
316    /// let jid = FullJid::new("node", "domain", "resource");
317    ///
318    /// assert_eq!(jid.domain, "domain".to_owned());
319    ///
320    /// let new_jid = jid.with_domain("new_domain");
321    ///
322    /// assert_eq!(new_jid.domain, "new_domain");
323    /// ```
324    pub fn with_domain<DS>(&self, domain: DS) -> FullJid
325    where
326        DS: Into<String>,
327    {
328        FullJid {
329            node: self.node.clone(),
330            domain: domain.into(),
331            resource: self.resource.clone(),
332        }
333    }
334
335    /// Constructs a Full Jabber ID from a Bare Jabber ID, specifying a `resource`.
336    ///
337    /// # Examples
338    ///
339    /// ```
340    /// use jid::FullJid;
341    ///
342    /// let jid = FullJid::new("node", "domain", "resource");
343    ///
344    /// assert_eq!(jid.resource, "resource".to_owned());
345    ///
346    /// let new_jid = jid.with_resource("new_resource");
347    ///
348    /// assert_eq!(new_jid.resource, "new_resource");
349    /// ```
350    pub fn with_resource<RS>(&self, resource: RS) -> FullJid
351    where
352        RS: Into<String>,
353    {
354        FullJid {
355            node: self.node.clone(),
356            domain: self.domain.clone(),
357            resource: resource.into(),
358        }
359    }
360}
361
362impl FromStr for BareJid {
363    type Err = JidParseError;
364
365    fn from_str(s: &str) -> Result<BareJid, JidParseError> {
366        let (ns, ds, _rs): StringJid = _from_str(s)?;
367        Ok(BareJid {
368            node: ns,
369            domain: ds,
370        })
371    }
372}
373
374impl BareJid {
375    /// Constructs a Bare Jabber ID, containing two components.
376    ///
377    /// This is of the form `node`@`domain`.
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// use jid::BareJid;
383    ///
384    /// let jid = BareJid::new("node", "domain");
385    ///
386    /// assert_eq!(jid.node, Some("node".to_owned()));
387    /// assert_eq!(jid.domain, "domain".to_owned());
388    /// ```
389    pub fn new<NS, DS>(node: NS, domain: DS) -> BareJid
390    where
391        NS: Into<String>,
392        DS: Into<String>,
393    {
394        BareJid {
395            node: Some(node.into()),
396            domain: domain.into(),
397        }
398    }
399
400    /// Constructs a Bare Jabber ID containing only a `domain`.
401    ///
402    /// This is of the form `domain`.
403    ///
404    /// # Examples
405    ///
406    /// ```
407    /// use jid::BareJid;
408    ///
409    /// let jid = BareJid::domain("domain");
410    ///
411    /// assert_eq!(jid.node, None);
412    /// assert_eq!(jid.domain, "domain".to_owned());
413    /// ```
414    pub fn domain<DS>(domain: DS) -> BareJid
415    where
416        DS: Into<String>,
417    {
418        BareJid {
419            node: None,
420            domain: domain.into(),
421        }
422    }
423
424    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
425    ///
426    /// # Examples
427    ///
428    /// ```
429    /// use jid::BareJid;
430    ///
431    /// let jid = BareJid::domain("domain");
432    ///
433    /// assert_eq!(jid.node, None);
434    ///
435    /// let new_jid = jid.with_node("node");
436    ///
437    /// assert_eq!(new_jid.node, Some("node".to_owned()));
438    /// ```
439    pub fn with_node<NS>(&self, node: NS) -> BareJid
440    where
441        NS: Into<String>,
442    {
443        BareJid {
444            node: Some(node.into()),
445            domain: self.domain.clone(),
446        }
447    }
448
449    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
450    ///
451    /// # Examples
452    ///
453    /// ```
454    /// use jid::BareJid;
455    ///
456    /// let jid = BareJid::domain("domain");
457    ///
458    /// assert_eq!(jid.domain, "domain");
459    ///
460    /// let new_jid = jid.with_domain("new_domain");
461    ///
462    /// assert_eq!(new_jid.domain, "new_domain");
463    /// ```
464    pub fn with_domain<DS>(&self, domain: DS) -> BareJid
465    where
466        DS: Into<String>,
467    {
468        BareJid {
469            node: self.node.clone(),
470            domain: domain.into(),
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::BareJid;
480    ///
481    /// let bare = BareJid::new("node", "domain");
482    /// let full = bare.with_resource("resource");
483    ///
484    /// assert_eq!(full.node, Some("node".to_owned()));
485    /// assert_eq!(full.domain, "domain".to_owned());
486    /// assert_eq!(full.resource, "resource".to_owned());
487    /// ```
488    pub fn with_resource<RS>(self, resource: RS) -> FullJid
489    where
490        RS: Into<String>,
491    {
492        FullJid {
493            node: self.node,
494            domain: self.domain,
495            resource: resource.into(),
496        }
497    }
498}
499
500#[cfg(feature = "minidom")]
501use minidom::{ElementEmitter, IntoAttributeValue, IntoElements};
502
503#[cfg(feature = "minidom")]
504impl IntoAttributeValue for Jid {
505    fn into_attribute_value(self) -> Option<String> {
506        Some(String::from(self))
507    }
508}
509
510#[cfg(feature = "minidom")]
511impl IntoElements for Jid {
512    fn into_elements(self, emitter: &mut ElementEmitter) {
513        emitter.append_text_node(String::from(self))
514    }
515}
516
517#[cfg(feature = "minidom")]
518impl IntoAttributeValue for FullJid {
519    fn into_attribute_value(self) -> Option<String> {
520        Some(String::from(self))
521    }
522}
523
524#[cfg(feature = "minidom")]
525impl IntoElements for FullJid {
526    fn into_elements(self, emitter: &mut ElementEmitter) {
527        emitter.append_text_node(String::from(self))
528    }
529}
530
531#[cfg(feature = "minidom")]
532impl IntoAttributeValue for BareJid {
533    fn into_attribute_value(self) -> Option<String> {
534        Some(String::from(self))
535    }
536}
537
538#[cfg(feature = "minidom")]
539impl IntoElements for BareJid {
540    fn into_elements(self, emitter: &mut ElementEmitter) {
541        emitter.append_text_node(String::from(self))
542    }
543}
544
545#[cfg(test)]
546mod tests {
547    use super::*;
548
549    use std::str::FromStr;
550
551    #[test]
552    fn can_parse_full_jids() {
553        assert_eq!(
554            FullJid::from_str("a@b.c/d"),
555            Ok(FullJid::new("a", "b.c", "d"))
556        );
557        assert_eq!(
558            FullJid::from_str("b.c/d"),
559            Ok(FullJid {
560                node: None,
561                domain: "b.c".to_owned(),
562                resource: "d".to_owned(),
563            })
564        );
565
566        assert_eq!(FullJid::from_str("a@b.c"), Err(JidParseError::NoResource));
567        assert_eq!(FullJid::from_str("b.c"), Err(JidParseError::NoResource));
568    }
569
570    #[test]
571    fn can_parse_bare_jids() {
572        assert_eq!(BareJid::from_str("a@b.c/d"), Ok(BareJid::new("a", "b.c")));
573        assert_eq!(
574            BareJid::from_str("b.c/d"),
575            Ok(BareJid {
576                node: None,
577                domain: "b.c".to_owned(),
578            })
579        );
580
581        assert_eq!(BareJid::from_str("a@b.c"), Ok(BareJid::new("a", "b.c")));
582        assert_eq!(
583            BareJid::from_str("b.c"),
584            Ok(BareJid {
585                node: None,
586                domain: "b.c".to_owned(),
587            })
588        );
589    }
590
591    #[test]
592    fn can_parse_jids() {
593        let full = FullJid::from_str("a@b.c/d").unwrap();
594        let bare = BareJid::from_str("e@f.g").unwrap();
595
596        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::Full(full)));
597        assert_eq!(Jid::from_str("e@f.g"), Ok(Jid::Bare(bare)));
598    }
599
600    #[test]
601    fn full_to_bare_jid() {
602        let bare: BareJid = FullJid::new("a", "b.c", "d").into();
603        assert_eq!(bare, BareJid::new("a", "b.c"));
604    }
605
606    #[test]
607    fn bare_to_full_jid() {
608        assert_eq!(
609            BareJid::new("a", "b.c").with_resource("d"),
610            FullJid::new("a", "b.c", "d")
611        );
612    }
613
614    #[test]
615    fn serialise() {
616        assert_eq!(
617            String::from(FullJid::new("a", "b", "c")),
618            String::from("a@b/c")
619        );
620        assert_eq!(String::from(BareJid::new("a", "b")), String::from("a@b"));
621    }
622
623    #[test]
624    fn invalid_jids() {
625        assert_eq!(BareJid::from_str(""), Err(JidParseError::NoDomain));
626        assert_eq!(BareJid::from_str("/c"), Err(JidParseError::NoDomain));
627        assert_eq!(BareJid::from_str("a@/c"), Err(JidParseError::NoDomain));
628        assert_eq!(BareJid::from_str("@b"), Err(JidParseError::EmptyNode));
629        assert_eq!(BareJid::from_str("b/"), Err(JidParseError::EmptyResource));
630
631        assert_eq!(FullJid::from_str(""), Err(JidParseError::NoDomain));
632        assert_eq!(FullJid::from_str("/c"), Err(JidParseError::NoDomain));
633        assert_eq!(FullJid::from_str("a@/c"), Err(JidParseError::NoDomain));
634        assert_eq!(FullJid::from_str("@b"), Err(JidParseError::EmptyNode));
635        assert_eq!(FullJid::from_str("b/"), Err(JidParseError::EmptyResource));
636        assert_eq!(FullJid::from_str("a@b"), Err(JidParseError::NoResource));
637    }
638
639    #[cfg(feature = "minidom")]
640    #[test]
641    fn minidom() {
642        let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
643        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
644        assert_eq!(to, Jid::Full(FullJid::new("a", "b", "c")));
645
646        let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
647        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
648        assert_eq!(to, Jid::Bare(BareJid::new("a", "b")));
649
650        let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
651        let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
652        assert_eq!(to, FullJid::new("a", "b", "c"));
653
654        let elem: minidom::Element = "<message from='a@b'/>".parse().unwrap();
655        let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
656        assert_eq!(to, BareJid::new("a", "b"));
657    }
658
659    #[cfg(feature = "minidom")]
660    #[test]
661    fn minidom_into_attr() {
662        let full = FullJid::new("a", "b", "c");
663        let elem = minidom::Element::builder("message")
664            .ns("jabber:client")
665            .attr("from", full.clone())
666            .build();
667        assert_eq!(elem.attr("from"), Some(String::from(full).as_ref()));
668
669        let bare = BareJid::new("a", "b");
670        let elem = minidom::Element::builder("message")
671            .ns("jabber:client")
672            .attr("from", bare.clone())
673            .build();
674        assert_eq!(elem.attr("from"), Some(String::from(bare.clone()).as_ref()));
675
676        let jid = Jid::Bare(bare.clone());
677        let _elem = minidom::Element::builder("message")
678            .ns("jabber:client")
679            .attr("from", jid)
680            .build();
681        assert_eq!(elem.attr("from"), Some(String::from(bare).as_ref()));
682    }
683}