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
  7use std::fmt;
  8
  9use std::convert::Into;
 10
 11use std::str::FromStr;
 12
 13/// An error that signifies that a `Jid` cannot be parsed from a string.
 14#[derive(Debug, Clone, PartialEq, Eq)]
 15pub enum JidParseError {
 16    /// Happens when there is no domain. (really, only happens when the string is empty)
 17    NoDomain,
 18}
 19
 20/// A struct representing a Jabber ID.
 21///
 22/// A Jabber ID is composed of 3 components, of which 2 are optional:
 23///
 24///  - A node/name, `node`, which is the optional part before the @.
 25///  - A domain, `domain`, which is the mandatory part after the @ but before the /.
 26///  - A resource, `resource`, which is the optional part after the /.
 27#[derive(Clone, PartialEq, Eq, Hash)]
 28pub struct Jid {
 29    /// The node part of the Jabber ID, if it exists, else None.
 30    pub node: Option<String>,
 31    /// The domain of the Jabber ID.
 32    pub domain: String,
 33    /// The resource of the Jabber ID, if it exists, else None.
 34    pub resource: Option<String>,
 35}
 36
 37impl From<Jid> for String {
 38    fn from(jid: Jid) -> String {
 39        let mut string = String::new();
 40        if let Some(ref node) = jid.node {
 41            string.push_str(node);
 42            string.push('@');
 43        }
 44        string.push_str(&jid.domain);
 45        if let Some(ref resource) = jid.resource {
 46            string.push('/');
 47            string.push_str(resource);
 48        }
 49        string
 50    }
 51}
 52
 53impl fmt::Debug for Jid {
 54    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 55        write!(fmt, "JID({})", self)?;
 56        Ok(())
 57    }
 58}
 59
 60impl fmt::Display for Jid {
 61    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 62        fmt.write_str(String::from(self.clone()).as_ref())?;
 63        Ok(())
 64    }
 65}
 66
 67enum ParserState {
 68    Node,
 69    Domain,
 70    Resource
 71}
 72
 73impl FromStr for Jid {
 74    type Err = JidParseError;
 75
 76    fn from_str(s: &str) -> Result<Jid, JidParseError> {
 77        // TODO: very naive, may need to do it differently
 78        let iter = s.chars();
 79        let mut buf = String::new();
 80        let mut state = ParserState::Node;
 81        let mut node = None;
 82        let mut domain = None;
 83        let mut resource = None;
 84        for c in iter {
 85            match state {
 86                ParserState::Node => {
 87                    match c {
 88                        '@' => {
 89                            state = ParserState::Domain;
 90                            node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
 91                            buf.clear();
 92                        },
 93                        '/' => {
 94                            state = ParserState::Resource;
 95                            domain = Some(buf.clone()); // TODO: performance tweaks
 96                            buf.clear();
 97                        },
 98                        c => {
 99                            buf.push(c);
100                        },
101                    }
102                },
103                ParserState::Domain => {
104                    match c {
105                        '/' => {
106                            state = ParserState::Resource;
107                            domain = Some(buf.clone()); // TODO: performance tweaks
108                            buf.clear();
109                        },
110                        c => {
111                            buf.push(c);
112                        },
113                    }
114                },
115                ParserState::Resource => {
116                    buf.push(c);
117                },
118            }
119        }
120        if !buf.is_empty() {
121            match state {
122                ParserState::Node => {
123                    domain = Some(buf);
124                },
125                ParserState::Domain => {
126                    domain = Some(buf);
127                },
128                ParserState::Resource => {
129                    resource = Some(buf);
130                },
131            }
132        }
133        Ok(Jid {
134            node: node,
135            domain: domain.ok_or(JidParseError::NoDomain)?,
136            resource: resource,
137        })
138    }
139}
140
141impl Jid {
142    /// Constructs a Jabber ID containing all three components.
143    ///
144    /// This is of the form `node`@`domain`/`resource`.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use jid::Jid;
150    ///
151    /// let jid = Jid::full("node", "domain", "resource");
152    ///
153    /// assert_eq!(jid.node, Some("node".to_owned()));
154    /// assert_eq!(jid.domain, "domain".to_owned());
155    /// assert_eq!(jid.resource, Some("resource".to_owned()));
156    /// ```
157    pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
158        where NS: Into<String>
159            , DS: Into<String>
160            , RS: Into<String> {
161        Jid {
162            node: Some(node.into()),
163            domain: domain.into(),
164            resource: Some(resource.into()),
165        }
166    }
167
168    /// Constructs a Jabber ID containing only the `node` and `domain` components.
169    ///
170    /// This is of the form `node`@`domain`.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use jid::Jid;
176    ///
177    /// let jid = Jid::bare("node", "domain");
178    ///
179    /// assert_eq!(jid.node, Some("node".to_owned()));
180    /// assert_eq!(jid.domain, "domain".to_owned());
181    /// assert_eq!(jid.resource, None);
182    /// ```
183    pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
184        where NS: Into<String>
185            , DS: Into<String> {
186        Jid {
187            node: Some(node.into()),
188            domain: domain.into(),
189            resource: None,
190        }
191    }
192
193    /// Returns a new Jabber ID from the current one with only node and domain.
194    ///
195    /// This is of the form `node`@`domain`.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// use jid::Jid;
201    ///
202    /// let jid = Jid::full("node", "domain", "resource").get_bare();
203    ///
204    /// assert_eq!(jid.node, Some("node".to_owned()));
205    /// assert_eq!(jid.domain, "domain".to_owned());
206    /// assert_eq!(jid.resource, None);
207    pub fn get_bare(self) -> Jid {
208        Jid {
209            node: self.node.clone(),
210            domain: self.domain.clone(),
211            resource: None,
212        }
213    }
214
215    /// Constructs a Jabber ID containing only a `domain`.
216    ///
217    /// This is of the form `domain`.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use jid::Jid;
223    ///
224    /// let jid = Jid::domain("domain");
225    ///
226    /// assert_eq!(jid.node, None);
227    /// assert_eq!(jid.domain, "domain".to_owned());
228    /// assert_eq!(jid.resource, None);
229    /// ```
230    pub fn domain<DS>(domain: DS) -> Jid
231        where DS: Into<String> {
232        Jid {
233            node: None,
234            domain: domain.into(),
235            resource: None,
236        }
237    }
238
239    /// Returns a new Jabber ID from the current one with only domain.
240    ///
241    /// This is of the form `domain`.
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// use jid::Jid;
247    ///
248    /// let jid = Jid::full("node", "domain", "resource").get_domain();
249    ///
250    /// assert_eq!(jid.node, None);
251    /// assert_eq!(jid.domain, "domain".to_owned());
252    /// assert_eq!(jid.resource, None);
253    pub fn get_domain(self) -> Jid {
254        Jid {
255            node: None,
256            domain: self.domain.clone(),
257            resource: None,
258        }
259    }
260
261    /// Constructs a Jabber ID containing the `domain` and `resource` components.
262    ///
263    /// This is of the form `domain`/`resource`.
264    ///
265    /// # Examples
266    ///
267    /// ```
268    /// use jid::Jid;
269    ///
270    /// let jid = Jid::domain_with_resource("domain", "resource");
271    ///
272    /// assert_eq!(jid.node, None);
273    /// assert_eq!(jid.domain, "domain".to_owned());
274    /// assert_eq!(jid.resource, Some("resource".to_owned()));
275    /// ```
276    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
277        where DS: Into<String>
278            , RS: Into<String> {
279        Jid {
280            node: None,
281            domain: domain.into(),
282            resource: Some(resource.into()),
283        }
284    }
285
286    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// use jid::Jid;
292    ///
293    /// let jid = Jid::domain("domain");
294    ///
295    /// assert_eq!(jid.node, None);
296    ///
297    /// let new_jid = jid.with_node("node");
298    ///
299    /// assert_eq!(new_jid.node, Some("node".to_owned()));
300    /// ```
301    pub fn with_node<S>(&self, node: S) -> Jid
302        where S: Into<String> {
303        Jid {
304            node: Some(node.into()),
305            domain: self.domain.clone(),
306            resource: self.resource.clone(),
307        }
308    }
309
310    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use jid::Jid;
316    ///
317    /// let jid = Jid::domain("domain");
318    ///
319    /// assert_eq!(jid.domain, "domain");
320    ///
321    /// let new_jid = jid.with_domain("new_domain");
322    ///
323    /// assert_eq!(new_jid.domain, "new_domain");
324    /// ```
325    pub fn with_domain<S>(&self, domain: S) -> Jid
326        where S: Into<String> {
327        Jid {
328            node: self.node.clone(),
329            domain: domain.into(),
330            resource: self.resource.clone(),
331        }
332    }
333
334    /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use jid::Jid;
340    ///
341    /// let jid = Jid::domain("domain");
342    ///
343    /// assert_eq!(jid.resource, None);
344    ///
345    /// let new_jid = jid.with_resource("resource");
346    ///
347    /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
348    /// ```
349    pub fn with_resource<S>(&self, resource: S) -> Jid
350        where S: Into<String> {
351        Jid {
352            node: self.node.clone(),
353            domain: self.domain.clone(),
354            resource: Some(resource.into()),
355        }
356    }
357
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363
364    use std::str::FromStr;
365
366    #[test]
367    fn can_parse_jids() {
368        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
369        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
370        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
371
372        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
373
374        assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
375    }
376
377    #[test]
378    fn serialise() {
379        assert_eq!(String::from(Jid::full("a", "b", "c")), String::from("a@b/c"));
380    }
381}