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)]
 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    /// Constructs a Jabber ID containing only a `domain`.
194    ///
195    /// This is of the form `domain`.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// use jid::Jid;
201    ///
202    /// let jid = Jid::domain("domain");
203    ///
204    /// assert_eq!(jid.node, None);
205    /// assert_eq!(jid.domain, "domain".to_owned());
206    /// assert_eq!(jid.resource, None);
207    /// ```
208    pub fn domain<DS>(domain: DS) -> Jid
209        where DS: Into<String> {
210        Jid {
211            node: None,
212            domain: domain.into(),
213            resource: None,
214        }
215    }
216
217    /// Constructs a Jabber ID containing the `domain` and `resource` components.
218    ///
219    /// This is of the form `domain`/`resource`.
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// use jid::Jid;
225    ///
226    /// let jid = Jid::domain_with_resource("domain", "resource");
227    ///
228    /// assert_eq!(jid.node, None);
229    /// assert_eq!(jid.domain, "domain".to_owned());
230    /// assert_eq!(jid.resource, Some("resource".to_owned()));
231    /// ```
232    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
233        where DS: Into<String>
234            , RS: Into<String> {
235        Jid {
236            node: None,
237            domain: domain.into(),
238            resource: Some(resource.into()),
239        }
240    }
241
242    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// use jid::Jid;
248    ///
249    /// let jid = Jid::domain("domain");
250    ///
251    /// assert_eq!(jid.node, None);
252    ///
253    /// let new_jid = jid.with_node("node");
254    ///
255    /// assert_eq!(new_jid.node, Some("node".to_owned()));
256    /// ```
257    pub fn with_node<S>(&self, node: S) -> Jid
258        where S: Into<String> {
259        Jid {
260            node: Some(node.into()),
261            domain: self.domain.clone(),
262            resource: self.resource.clone(),
263        }
264    }
265
266    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// use jid::Jid;
272    ///
273    /// let jid = Jid::domain("domain");
274    ///
275    /// assert_eq!(jid.domain, "domain");
276    ///
277    /// let new_jid = jid.with_domain("new_domain");
278    ///
279    /// assert_eq!(new_jid.domain, "new_domain");
280    /// ```
281    pub fn with_domain<S>(&self, domain: S) -> Jid
282        where S: Into<String> {
283        Jid {
284            node: self.node.clone(),
285            domain: domain.into(),
286            resource: self.resource.clone(),
287        }
288    }
289
290    /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
291    ///
292    /// # Examples
293    ///
294    /// ```
295    /// use jid::Jid;
296    ///
297    /// let jid = Jid::domain("domain");
298    ///
299    /// assert_eq!(jid.resource, None);
300    ///
301    /// let new_jid = jid.with_resource("resource");
302    ///
303    /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
304    /// ```
305    pub fn with_resource<S>(&self, resource: S) -> Jid
306        where S: Into<String> {
307        Jid {
308            node: self.node.clone(),
309            domain: self.domain.clone(),
310            resource: Some(resource.into()),
311        }
312    }
313
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    use std::str::FromStr;
321
322    #[test]
323    fn can_parse_jids() {
324        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
325        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
326        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
327
328        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
329
330        assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
331    }
332
333    #[test]
334    fn serialise() {
335        assert_eq!(String::from(Jid::full("a", "b", "c")), String::from("a@b/c"));
336    }
337}