1//! Provides a type for Jabber IDs.
2
3use std::fmt;
4
5use std::convert::Into;
6
7use std::str::FromStr;
8
9/// An error that signifies that a `Jid` cannot be parsed from a string.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum JidParseError {
12 NoDomain,
13}
14
15/// A struct representing a Jabber ID.
16///
17/// A Jabber ID is composed of 3 components, of which 2 are optional:
18///
19/// - A node/name, `node`, which is the optional part before the @.
20/// - A domain, `domain`, which is the mandatory part after the @ but before the /.
21/// - A resource, `resource`, which is the optional part after the /.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct Jid {
24 /// The node part of the Jabber ID, if it exists, else None.
25 pub node: Option<String>,
26 /// The domain of the Jabber ID.
27 pub domain: String,
28 /// The resource of the Jabber ID, if it exists, else None.
29 pub resource: Option<String>,
30}
31
32impl fmt::Display for Jid {
33 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
34 // TODO: may need escaping
35 if let Some(ref node) = self.node {
36 write!(fmt, "{}@", node)?;
37 }
38 write!(fmt, "{}", self.domain)?;
39 if let Some(ref resource) = self.resource {
40 write!(fmt, "/{}", resource)?;
41 }
42 Ok(())
43 }
44}
45
46enum ParserState {
47 Node,
48 Domain,
49 Resource
50}
51
52impl FromStr for Jid {
53 type Err = JidParseError;
54
55 fn from_str(s: &str) -> Result<Jid, JidParseError> {
56 // TODO: very naive, may need to do it differently
57 let iter = s.chars();
58 let mut buf = String::new();
59 let mut state = ParserState::Node;
60 let mut node = None;
61 let mut domain = None;
62 let mut resource = None;
63 for c in iter {
64 match state {
65 ParserState::Node => {
66 match c {
67 '@' => {
68 state = ParserState::Domain;
69 node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
70 buf.clear();
71 },
72 '/' => {
73 state = ParserState::Resource;
74 domain = Some(buf.clone()); // TODO: performance tweaks
75 buf.clear();
76 },
77 c => {
78 buf.push(c);
79 },
80 }
81 },
82 ParserState::Domain => {
83 match c {
84 '/' => {
85 state = ParserState::Resource;
86 domain = Some(buf.clone()); // TODO: performance tweaks
87 buf.clear();
88 },
89 c => {
90 buf.push(c);
91 },
92 }
93 },
94 ParserState::Resource => {
95 buf.push(c);
96 },
97 }
98 }
99 if !buf.is_empty() {
100 match state {
101 ParserState::Node => {
102 domain = Some(buf);
103 },
104 ParserState::Domain => {
105 domain = Some(buf);
106 },
107 ParserState::Resource => {
108 resource = Some(buf);
109 },
110 }
111 }
112 Ok(Jid {
113 node: node,
114 domain: domain.ok_or(JidParseError::NoDomain)?,
115 resource: resource,
116 })
117 }
118}
119
120impl Jid {
121 /// Constructs a Jabber ID containing all three components.
122 ///
123 /// This is of the form `node`@`domain`/`resource`.
124 ///
125 /// # Examples
126 ///
127 /// ```
128 /// use jid::Jid;
129 ///
130 /// let jid = Jid::full("node", "domain", "resource");
131 ///
132 /// assert_eq!(jid.node, Some("node".to_owned()));
133 /// assert_eq!(jid.domain, "domain".to_owned());
134 /// assert_eq!(jid.resource, Some("resource".to_owned()));
135 /// ```
136 pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
137 where NS: Into<String>
138 , DS: Into<String>
139 , RS: Into<String> {
140 Jid {
141 node: Some(node.into()),
142 domain: domain.into(),
143 resource: Some(resource.into()),
144 }
145 }
146
147 /// Constructs a Jabber ID containing only the `node` and `domain` components.
148 ///
149 /// This is of the form `node`@`domain`.
150 ///
151 /// # Examples
152 ///
153 /// ```
154 /// use jid::Jid;
155 ///
156 /// let jid = Jid::bare("node", "domain");
157 ///
158 /// assert_eq!(jid.node, Some("node".to_owned()));
159 /// assert_eq!(jid.domain, "domain".to_owned());
160 /// assert_eq!(jid.resource, None);
161 /// ```
162 pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
163 where NS: Into<String>
164 , DS: Into<String> {
165 Jid {
166 node: Some(node.into()),
167 domain: domain.into(),
168 resource: None,
169 }
170 }
171
172 /// Constructs a Jabber ID containing only a `domain`.
173 ///
174 /// This is of the form `domain`.
175 ///
176 /// # Examples
177 ///
178 /// ```
179 /// use jid::Jid;
180 ///
181 /// let jid = Jid::domain("domain");
182 ///
183 /// assert_eq!(jid.node, None);
184 /// assert_eq!(jid.domain, "domain".to_owned());
185 /// assert_eq!(jid.resource, None);
186 /// ```
187 pub fn domain<DS>(domain: DS) -> Jid
188 where DS: Into<String> {
189 Jid {
190 node: None,
191 domain: domain.into(),
192 resource: None,
193 }
194 }
195
196 /// Constructs a Jabber ID containing the `domain` and `resource` components.
197 ///
198 /// This is of the form `domain`/`resource`.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use jid::Jid;
204 ///
205 /// let jid = Jid::domain_with_resource("domain", "resource");
206 ///
207 /// assert_eq!(jid.node, None);
208 /// assert_eq!(jid.domain, "domain".to_owned());
209 /// assert_eq!(jid.resource, Some("resource".to_owned()));
210 /// ```
211 pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
212 where DS: Into<String>
213 , RS: Into<String> {
214 Jid {
215 node: None,
216 domain: domain.into(),
217 resource: Some(resource.into()),
218 }
219 }
220
221 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use jid::Jid;
227 ///
228 /// let jid = Jid::domain("domain");
229 ///
230 /// assert_eq!(jid.node, None);
231 ///
232 /// let new_jid = jid.with_node("node");
233 ///
234 /// assert_eq!(new_jid.node, Some("node".to_owned()));
235 /// ```
236 pub fn with_node<S>(&self, node: S) -> Jid
237 where S: Into<String> {
238 Jid {
239 node: Some(node.into()),
240 domain: self.domain.clone(),
241 resource: self.resource.clone(),
242 }
243 }
244
245 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
246 ///
247 /// # Examples
248 ///
249 /// ```
250 /// use jid::Jid;
251 ///
252 /// let jid = Jid::domain("domain");
253 ///
254 /// assert_eq!(jid.domain, "domain");
255 ///
256 /// let new_jid = jid.with_domain("new_domain");
257 ///
258 /// assert_eq!(new_jid.domain, "new_domain");
259 /// ```
260 pub fn with_domain<S>(&self, domain: S) -> Jid
261 where S: Into<String> {
262 Jid {
263 node: self.node.clone(),
264 domain: domain.into(),
265 resource: self.resource.clone(),
266 }
267 }
268
269 /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
270 ///
271 /// # Examples
272 ///
273 /// ```
274 /// use jid::Jid;
275 ///
276 /// let jid = Jid::domain("domain");
277 ///
278 /// assert_eq!(jid.resource, None);
279 ///
280 /// let new_jid = jid.with_resource("resource");
281 ///
282 /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
283 /// ```
284 pub fn with_resource<S>(&self, resource: S) -> Jid
285 where S: Into<String> {
286 Jid {
287 node: self.node.clone(),
288 domain: self.domain.clone(),
289 resource: Some(resource.into()),
290 }
291 }
292
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 use std::str::FromStr;
300
301 #[test]
302 fn can_parse_jids() {
303 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
304 assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
305 assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
306
307 assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
308
309 assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
310 }
311}