jid.rs

  1use std::fmt;
  2
  3use std::convert::Into;
  4
  5use std::str::FromStr;
  6
  7use std::string::ToString;
  8
  9#[derive(Debug, Clone, PartialEq, Eq)]
 10pub enum JidParseError {
 11    NoDomain,
 12}
 13
 14#[derive(Debug, Clone, PartialEq, Eq)]
 15pub struct Jid {
 16    pub node: Option<String>,
 17    pub domain: String,
 18    pub resource: Option<String>,
 19}
 20
 21impl fmt::Display for Jid {
 22    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
 23        // TODO: may need escaping
 24        if let Some(ref node) = self.node {
 25            write!(fmt, "{}@", node)?;
 26        }
 27        write!(fmt, "{}", self.domain)?;
 28        if let Some(ref resource) = self.resource {
 29            write!(fmt, "/{}", resource)?;
 30        }
 31        Ok(())
 32    }
 33}
 34
 35enum ParserState {
 36    Node,
 37    Domain,
 38    Resource
 39}
 40
 41impl FromStr for Jid {
 42    type Err = JidParseError;
 43
 44    fn from_str(s: &str) -> Result<Jid, JidParseError> {
 45        // TODO: very naive, may need to do it differently
 46        let mut iter = s.chars();
 47        let mut buf = String::new();
 48        let mut state = ParserState::Node;
 49        let mut node = None;
 50        let mut domain = None;
 51        let mut resource = None;
 52        for c in iter {
 53            match state {
 54                ParserState::Node => {
 55                    match c {
 56                        '@' => {
 57                            state = ParserState::Domain;
 58                            node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
 59                            buf.clear();
 60                        },
 61                        '/' => {
 62                            state = ParserState::Resource;
 63                            domain = Some(buf.clone()); // TODO: performance tweaks
 64                            buf.clear();
 65                        },
 66                        c => {
 67                            buf.push(c);
 68                        },
 69                    }
 70                },
 71                ParserState::Domain => {
 72                    match c {
 73                        '/' => {
 74                            state = ParserState::Resource;
 75                            domain = Some(buf.clone()); // TODO: performance tweaks
 76                            buf.clear();
 77                        },
 78                        c => {
 79                            buf.push(c);
 80                        },
 81                    }
 82                },
 83                ParserState::Resource => {
 84                    buf.push(c);
 85                },
 86            }
 87        }
 88        if !buf.is_empty() {
 89            match state {
 90                ParserState::Node => {
 91                    domain = Some(buf);
 92                },
 93                ParserState::Domain => {
 94                    domain = Some(buf);
 95                },
 96                ParserState::Resource => {
 97                    resource = Some(buf);
 98                },
 99            }
100        }
101        Ok(Jid {
102            node: node,
103            domain: domain.ok_or(JidParseError::NoDomain)?,
104            resource: resource,
105        })
106    }
107}
108
109impl Jid {
110    pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
111        where NS: Into<String>
112            , DS: Into<String>
113            , RS: Into<String> {
114        Jid {
115            node: Some(node.into()),
116            domain: domain.into(),
117            resource: Some(resource.into()),
118        }
119    }
120
121    pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
122        where NS: Into<String>
123            , DS: Into<String> {
124        Jid {
125            node: Some(node.into()),
126            domain: domain.into(),
127            resource: None,
128        }
129    }
130
131    pub fn domain<DS>(domain: DS) -> Jid
132        where DS: Into<String> {
133        Jid {
134            node: None,
135            domain: domain.into(),
136            resource: None,
137        }
138    }
139
140    pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
141        where DS: Into<String>
142            , RS: Into<String> {
143        Jid {
144            node: None,
145            domain: domain.into(),
146            resource: Some(resource.into()),
147        }
148    }
149}
150
151#[cfg(test)]
152mod test {
153    use super::*;
154
155    #[test]
156    fn can_parse_jids() {
157        assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
158        assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
159        assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
160
161        assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
162
163        assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
164    }
165}