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