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::with_capacity(s.len());
 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").into_bare_jid();
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    /// ```
208    pub fn into_bare_jid(self) -> Jid {
209        Jid {
210            node: self.node,
211            domain: self.domain,
212            resource: None,
213        }
214    }
215
216    /// Constructs a Jabber ID containing only a `domain`.
217    ///
218    /// This is of the form `domain`.
219    ///
220    /// # Examples
221    ///
222    /// ```
223    /// use jid::Jid;
224    ///
225    /// let jid = Jid::domain("domain");
226    ///
227    /// assert_eq!(jid.node, None);
228    /// assert_eq!(jid.domain, "domain".to_owned());
229    /// assert_eq!(jid.resource, None);
230    /// ```
231    pub fn domain<DS>(domain: DS) -> Jid
232        where DS: Into<String> {
233        Jid {
234            node: None,
235            domain: domain.into(),
236            resource: None,
237        }
238    }
239
240    /// Returns a new Jabber ID from the current one with only domain.
241    ///
242    /// This is of the form `domain`.
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// use jid::Jid;
248    ///
249    /// let jid = Jid::full("node", "domain", "resource").into_domain_jid();
250    ///
251    /// assert_eq!(jid.node, None);
252    /// assert_eq!(jid.domain, "domain".to_owned());
253    /// assert_eq!(jid.resource, None);
254    /// ```
255    pub fn into_domain_jid(self) -> Jid {
256        Jid {
257            node: None,
258            domain: self.domain,
259            resource: None,
260        }
261    }
262
263    /// Constructs a Jabber ID containing the `domain` and `resource` components.
264    ///
265    /// This is of the form `domain`/`resource`.
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// use jid::Jid;
271    ///
272    /// let jid = Jid::domain_with_resource("domain", "resource");
273    ///
274    /// assert_eq!(jid.node, None);
275    /// assert_eq!(jid.domain, "domain".to_owned());
276    /// assert_eq!(jid.resource, Some("resource".to_owned()));
277    /// ```
278    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
279        where DS: Into<String>
280            , RS: Into<String> {
281        Jid {
282            node: None,
283            domain: domain.into(),
284            resource: Some(resource.into()),
285        }
286    }
287
288    /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
289    ///
290    /// # Examples
291    ///
292    /// ```
293    /// use jid::Jid;
294    ///
295    /// let jid = Jid::domain("domain");
296    ///
297    /// assert_eq!(jid.node, None);
298    ///
299    /// let new_jid = jid.with_node("node");
300    ///
301    /// assert_eq!(new_jid.node, Some("node".to_owned()));
302    /// ```
303    pub fn with_node<S>(&self, node: S) -> Jid
304        where S: Into<String> {
305        Jid {
306            node: Some(node.into()),
307            domain: self.domain.clone(),
308            resource: self.resource.clone(),
309        }
310    }
311
312    /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
313    ///
314    /// # Examples
315    ///
316    /// ```
317    /// use jid::Jid;
318    ///
319    /// let jid = Jid::domain("domain");
320    ///
321    /// assert_eq!(jid.domain, "domain");
322    ///
323    /// let new_jid = jid.with_domain("new_domain");
324    ///
325    /// assert_eq!(new_jid.domain, "new_domain");
326    /// ```
327    pub fn with_domain<S>(&self, domain: S) -> Jid
328        where S: Into<String> {
329        Jid {
330            node: self.node.clone(),
331            domain: domain.into(),
332            resource: self.resource.clone(),
333        }
334    }
335
336    /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// use jid::Jid;
342    ///
343    /// let jid = Jid::domain("domain");
344    ///
345    /// assert_eq!(jid.resource, None);
346    ///
347    /// let new_jid = jid.with_resource("resource");
348    ///
349    /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
350    /// ```
351    pub fn with_resource<S>(&self, resource: S) -> Jid
352        where S: Into<String> {
353        Jid {
354            node: self.node.clone(),
355            domain: self.domain.clone(),
356            resource: Some(resource.into()),
357        }
358    }
359
360}
361
362#[cfg(feature = "minidom")]
363extern crate minidom;
364
365#[cfg(feature = "minidom")]
366use minidom::IntoAttributeValue;
367
368#[cfg(feature = "minidom")]
369impl IntoAttributeValue for Jid {
370    fn into_attribute_value(self) -> Option<String> {
371        Some(String::from(self))
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378
379    use std::str::FromStr;
380
381    #[test]
382    fn can_parse_jids() {
383        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
384        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
385        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
386
387        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
388
389        assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
390    }
391
392    #[test]
393    fn serialise() {
394        assert_eq!(String::from(Jid::full("a", "b", "c")), String::from("a@b/c"));
395    }
396
397    #[cfg(feature = "minidom")]
398    #[test]
399    fn minidom() {
400        let elem: minidom::Element = "<message from='a@b/c'/>".parse().unwrap();
401        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
402        assert_eq!(to, Jid::full("a", "b", "c"));
403    }
404}