jid.rs

  1//! Provides a type for Jabber IDs.
  2
  3use std::fmt;
  4
  5use std::convert::Into;
  6
  7use std::str::FromStr;
  8
  9/// An error that signifies that a `Jid` cannot be parsed from a string.
 10#[derive(Debug, Clone, PartialEq, Eq)]
 11pub enum JidParseError {
 12    NoDomain,
 13}
 14
 15/// A struct representing a Jabber ID.
 16///
 17/// A Jabber ID is composed of 3 components, of which 2 are optional:
 18///
 19///  - A node/name, `node`, which is the optional part before the @.
 20///  - A domain, `domain`, which is the mandatory part after the @ but before the /.
 21///  - A resource, `resource`, which is the optional part after the /.
 22#[derive(Debug, Clone, PartialEq, Eq)]
 23pub struct Jid {
 24    /// The node part of the Jabber ID, if it exists, else None.
 25    pub node: Option<String>,
 26    /// The domain of the Jabber ID.
 27    pub domain: String,
 28    /// The resource of the Jabber ID, if it exists, else None.
 29    pub resource: Option<String>,
 30}
 31
 32impl fmt::Display for Jid {
 33    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 34        // TODO: may need escaping
 35        if let Some(ref node) = self.node {
 36            write!(fmt, "{}@", node)?;
 37        }
 38        write!(fmt, "{}", self.domain)?;
 39        if let Some(ref resource) = self.resource {
 40            write!(fmt, "/{}", resource)?;
 41        }
 42        Ok(())
 43    }
 44}
 45
 46enum ParserState {
 47    Node,
 48    Domain,
 49    Resource
 50}
 51
 52impl FromStr for Jid {
 53    type Err = JidParseError;
 54
 55    fn from_str(s: &str) -> Result<Jid, JidParseError> {
 56        // TODO: very naive, may need to do it differently
 57        let iter = s.chars();
 58        let mut buf = String::new();
 59        let mut state = ParserState::Node;
 60        let mut node = None;
 61        let mut domain = None;
 62        let mut resource = None;
 63        for c in iter {
 64            match state {
 65                ParserState::Node => {
 66                    match c {
 67                        '@' => {
 68                            state = ParserState::Domain;
 69                            node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
 70                            buf.clear();
 71                        },
 72                        '/' => {
 73                            state = ParserState::Resource;
 74                            domain = Some(buf.clone()); // TODO: performance tweaks
 75                            buf.clear();
 76                        },
 77                        c => {
 78                            buf.push(c);
 79                        },
 80                    }
 81                },
 82                ParserState::Domain => {
 83                    match c {
 84                        '/' => {
 85                            state = ParserState::Resource;
 86                            domain = Some(buf.clone()); // TODO: performance tweaks
 87                            buf.clear();
 88                        },
 89                        c => {
 90                            buf.push(c);
 91                        },
 92                    }
 93                },
 94                ParserState::Resource => {
 95                    buf.push(c);
 96                },
 97            }
 98        }
 99        if !buf.is_empty() {
100            match state {
101                ParserState::Node => {
102                    domain = Some(buf);
103                },
104                ParserState::Domain => {
105                    domain = Some(buf);
106                },
107                ParserState::Resource => {
108                    resource = Some(buf);
109                },
110            }
111        }
112        Ok(Jid {
113            node: node,
114            domain: domain.ok_or(JidParseError::NoDomain)?,
115            resource: resource,
116        })
117    }
118}
119
120impl Jid {
121    /// Constructs a Jabber ID containing all three components.
122    ///
123    /// This is of the form `node`@`domain`/`resource`.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use xmpp::jid::Jid;
129    ///
130    /// let jid = Jid::full("node", "domain", "resource");
131    ///
132    /// assert_eq!(jid.node, Some("node".to_owned()));
133    /// assert_eq!(jid.domain, "domain".to_owned());
134    /// assert_eq!(jid.resource, Some("resource".to_owned()));
135    /// ```
136    pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
137        where NS: Into<String>
138            , DS: Into<String>
139            , RS: Into<String> {
140        Jid {
141            node: Some(node.into()),
142            domain: domain.into(),
143            resource: Some(resource.into()),
144        }
145    }
146
147    /// Constructs a Jabber ID containing only the `node` and `domain` components.
148    ///
149    /// This is of the form `node`@`domain`.
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// use xmpp::jid::Jid;
155    ///
156    /// let jid = Jid::bare("node", "domain");
157    ///
158    /// assert_eq!(jid.node, Some("node".to_owned()));
159    /// assert_eq!(jid.domain, "domain".to_owned());
160    /// assert_eq!(jid.resource, None);
161    /// ```
162    pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
163        where NS: Into<String>
164            , DS: Into<String> {
165        Jid {
166            node: Some(node.into()),
167            domain: domain.into(),
168            resource: None,
169        }
170    }
171
172    /// Constructs a Jabber ID containing only a `domain`.
173    ///
174    /// This is of the form `domain`.
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// use xmpp::jid::Jid;
180    ///
181    /// let jid = Jid::domain("domain");
182    ///
183    /// assert_eq!(jid.node, None);
184    /// assert_eq!(jid.domain, "domain".to_owned());
185    /// assert_eq!(jid.resource, None);
186    /// ```
187    pub fn domain<DS>(domain: DS) -> Jid
188        where DS: Into<String> {
189        Jid {
190            node: None,
191            domain: domain.into(),
192            resource: None,
193        }
194    }
195
196    /// Constructs a Jabber ID containing the `domain` and `resource` components.
197    ///
198    /// This is of the form `domain`/`resource`.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use xmpp::jid::Jid;
204    ///
205    /// let jid = Jid::domain_with_resource("domain", "resource");
206    ///
207    /// assert_eq!(jid.node, None);
208    /// assert_eq!(jid.domain, "domain".to_owned());
209    /// assert_eq!(jid.resource, Some("resource".to_owned()));
210    /// ```
211    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
212        where DS: Into<String>
213            , RS: Into<String> {
214        Jid {
215            node: None,
216            domain: domain.into(),
217            resource: Some(resource.into()),
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    use std::str::FromStr;
227
228    #[test]
229    fn can_parse_jids() {
230        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
231        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
232        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
233
234        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
235
236        assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
237    }
238}