lib.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 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 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 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 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    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// use jid::Jid;
227    ///
228    /// let jid = Jid::domain("domain");
229    ///
230    /// assert_eq!(jid.node, None);
231    ///
232    /// let new_jid = jid.with_node("node");
233    ///
234    /// assert_eq!(new_jid.node, Some("node".to_owned()));
235    /// ```
236    pub fn with_node<S>(&self, node: S) -> Jid
237        where S: Into<String> {
238        Jid {
239            node: Some(node.into()),
240            domain: self.domain.clone(),
241            resource: self.resource.clone(),
242        }
243    }
244
245    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
246    ///
247    /// # Examples
248    ///
249    /// ```
250    /// use jid::Jid;
251    ///
252    /// let jid = Jid::domain("domain");
253    ///
254    /// assert_eq!(jid.domain, "domain");
255    ///
256    /// let new_jid = jid.with_domain("new_domain");
257    ///
258    /// assert_eq!(new_jid.domain, "new_domain");
259    /// ```
260    pub fn with_domain<S>(&self, domain: S) -> Jid
261        where S: Into<String> {
262        Jid {
263            node: self.node.clone(),
264            domain: domain.into(),
265            resource: self.resource.clone(),
266        }
267    }
268
269    /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
270    ///
271    /// # Examples
272    ///
273    /// ```
274    /// use jid::Jid;
275    ///
276    /// let jid = Jid::domain("domain");
277    ///
278    /// assert_eq!(jid.resource, None);
279    ///
280    /// let new_jid = jid.with_resource("resource");
281    ///
282    /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
283    /// ```
284    pub fn with_resource<S>(&self, resource: S) -> Jid
285        where S: Into<String> {
286        Jid {
287            node: self.node.clone(),
288            domain: self.domain.clone(),
289            resource: Some(resource.into()),
290        }
291    }
292
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    use std::str::FromStr;
300
301    #[test]
302    fn can_parse_jids() {
303        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
304        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
305        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
306
307        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
308
309        assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
310    }
311}