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}