jid.rs

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