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 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}