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(Debug, Clone, PartialEq, Eq)]
 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 fmt::Display for Jid {
 38    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 39        // TODO: may need escaping
 40        if let Some(ref node) = self.node {
 41            write!(fmt, "{}@", node)?;
 42        }
 43        write!(fmt, "{}", self.domain)?;
 44        if let Some(ref resource) = self.resource {
 45            write!(fmt, "/{}", resource)?;
 46        }
 47        Ok(())
 48    }
 49}
 50
 51enum ParserState {
 52    Node,
 53    Domain,
 54    Resource
 55}
 56
 57impl FromStr for Jid {
 58    type Err = JidParseError;
 59
 60    fn from_str(s: &str) -> Result<Jid, JidParseError> {
 61        // TODO: very naive, may need to do it differently
 62        let iter = s.chars();
 63        let mut buf = String::new();
 64        let mut state = ParserState::Node;
 65        let mut node = None;
 66        let mut domain = None;
 67        let mut resource = None;
 68        for c in iter {
 69            match state {
 70                ParserState::Node => {
 71                    match c {
 72                        '@' => {
 73                            state = ParserState::Domain;
 74                            node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
 75                            buf.clear();
 76                        },
 77                        '/' => {
 78                            state = ParserState::Resource;
 79                            domain = Some(buf.clone()); // TODO: performance tweaks
 80                            buf.clear();
 81                        },
 82                        c => {
 83                            buf.push(c);
 84                        },
 85                    }
 86                },
 87                ParserState::Domain => {
 88                    match c {
 89                        '/' => {
 90                            state = ParserState::Resource;
 91                            domain = Some(buf.clone()); // TODO: performance tweaks
 92                            buf.clear();
 93                        },
 94                        c => {
 95                            buf.push(c);
 96                        },
 97                    }
 98                },
 99                ParserState::Resource => {
100                    buf.push(c);
101                },
102            }
103        }
104        if !buf.is_empty() {
105            match state {
106                ParserState::Node => {
107                    domain = Some(buf);
108                },
109                ParserState::Domain => {
110                    domain = Some(buf);
111                },
112                ParserState::Resource => {
113                    resource = Some(buf);
114                },
115            }
116        }
117        Ok(Jid {
118            node: node,
119            domain: domain.ok_or(JidParseError::NoDomain)?,
120            resource: resource,
121        })
122    }
123}
124
125impl Jid {
126    /// Constructs a Jabber ID containing all three components.
127    ///
128    /// This is of the form `node`@`domain`/`resource`.
129    ///
130    /// # Examples
131    ///
132    /// ```
133    /// use jid::Jid;
134    ///
135    /// let jid = Jid::full("node", "domain", "resource");
136    ///
137    /// assert_eq!(jid.node, Some("node".to_owned()));
138    /// assert_eq!(jid.domain, "domain".to_owned());
139    /// assert_eq!(jid.resource, Some("resource".to_owned()));
140    /// ```
141    pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
142        where NS: Into<String>
143            , DS: Into<String>
144            , RS: Into<String> {
145        Jid {
146            node: Some(node.into()),
147            domain: domain.into(),
148            resource: Some(resource.into()),
149        }
150    }
151
152    /// Constructs a Jabber ID containing only the `node` and `domain` components.
153    ///
154    /// This is of the form `node`@`domain`.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use jid::Jid;
160    ///
161    /// let jid = Jid::bare("node", "domain");
162    ///
163    /// assert_eq!(jid.node, Some("node".to_owned()));
164    /// assert_eq!(jid.domain, "domain".to_owned());
165    /// assert_eq!(jid.resource, None);
166    /// ```
167    pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
168        where NS: Into<String>
169            , DS: Into<String> {
170        Jid {
171            node: Some(node.into()),
172            domain: domain.into(),
173            resource: None,
174        }
175    }
176
177    /// Constructs a Jabber ID containing only a `domain`.
178    ///
179    /// This is of the form `domain`.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use jid::Jid;
185    ///
186    /// let jid = Jid::domain("domain");
187    ///
188    /// assert_eq!(jid.node, None);
189    /// assert_eq!(jid.domain, "domain".to_owned());
190    /// assert_eq!(jid.resource, None);
191    /// ```
192    pub fn domain<DS>(domain: DS) -> Jid
193        where DS: Into<String> {
194        Jid {
195            node: None,
196            domain: domain.into(),
197            resource: None,
198        }
199    }
200
201    /// Constructs a Jabber ID containing the `domain` and `resource` components.
202    ///
203    /// This is of the form `domain`/`resource`.
204    ///
205    /// # Examples
206    ///
207    /// ```
208    /// use jid::Jid;
209    ///
210    /// let jid = Jid::domain_with_resource("domain", "resource");
211    ///
212    /// assert_eq!(jid.node, None);
213    /// assert_eq!(jid.domain, "domain".to_owned());
214    /// assert_eq!(jid.resource, Some("resource".to_owned()));
215    /// ```
216    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
217        where DS: Into<String>
218            , RS: Into<String> {
219        Jid {
220            node: None,
221            domain: domain.into(),
222            resource: Some(resource.into()),
223        }
224    }
225
226    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use jid::Jid;
232    ///
233    /// let jid = Jid::domain("domain");
234    ///
235    /// assert_eq!(jid.node, None);
236    ///
237    /// let new_jid = jid.with_node("node");
238    ///
239    /// assert_eq!(new_jid.node, Some("node".to_owned()));
240    /// ```
241    pub fn with_node<S>(&self, node: S) -> Jid
242        where S: Into<String> {
243        Jid {
244            node: Some(node.into()),
245            domain: self.domain.clone(),
246            resource: self.resource.clone(),
247        }
248    }
249
250    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
251    ///
252    /// # Examples
253    ///
254    /// ```
255    /// use jid::Jid;
256    ///
257    /// let jid = Jid::domain("domain");
258    ///
259    /// assert_eq!(jid.domain, "domain");
260    ///
261    /// let new_jid = jid.with_domain("new_domain");
262    ///
263    /// assert_eq!(new_jid.domain, "new_domain");
264    /// ```
265    pub fn with_domain<S>(&self, domain: S) -> Jid
266        where S: Into<String> {
267        Jid {
268            node: self.node.clone(),
269            domain: domain.into(),
270            resource: self.resource.clone(),
271        }
272    }
273
274    /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use jid::Jid;
280    ///
281    /// let jid = Jid::domain("domain");
282    ///
283    /// assert_eq!(jid.resource, None);
284    ///
285    /// let new_jid = jid.with_resource("resource");
286    ///
287    /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
288    /// ```
289    pub fn with_resource<S>(&self, resource: S) -> Jid
290        where S: Into<String> {
291        Jid {
292            node: self.node.clone(),
293            domain: self.domain.clone(),
294            resource: Some(resource.into()),
295        }
296    }
297
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    use std::str::FromStr;
305
306    #[test]
307    fn can_parse_jids() {
308        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
309        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
310        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
311
312        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
313
314        assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
315    }
316}