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(Debug, Clone, PartialEq, Eq)]
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::Display for Jid {
54 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
55 fmt.write_str(String::from(self.clone()).as_ref())?;
56 Ok(())
57 }
58}
59
60enum ParserState {
61 Node,
62 Domain,
63 Resource
64}
65
66impl FromStr for Jid {
67 type Err = JidParseError;
68
69 fn from_str(s: &str) -> Result<Jid, JidParseError> {
70 // TODO: very naive, may need to do it differently
71 let iter = s.chars();
72 let mut buf = String::new();
73 let mut state = ParserState::Node;
74 let mut node = None;
75 let mut domain = None;
76 let mut resource = None;
77 for c in iter {
78 match state {
79 ParserState::Node => {
80 match c {
81 '@' => {
82 state = ParserState::Domain;
83 node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
84 buf.clear();
85 },
86 '/' => {
87 state = ParserState::Resource;
88 domain = Some(buf.clone()); // TODO: performance tweaks
89 buf.clear();
90 },
91 c => {
92 buf.push(c);
93 },
94 }
95 },
96 ParserState::Domain => {
97 match c {
98 '/' => {
99 state = ParserState::Resource;
100 domain = Some(buf.clone()); // TODO: performance tweaks
101 buf.clear();
102 },
103 c => {
104 buf.push(c);
105 },
106 }
107 },
108 ParserState::Resource => {
109 buf.push(c);
110 },
111 }
112 }
113 if !buf.is_empty() {
114 match state {
115 ParserState::Node => {
116 domain = Some(buf);
117 },
118 ParserState::Domain => {
119 domain = Some(buf);
120 },
121 ParserState::Resource => {
122 resource = Some(buf);
123 },
124 }
125 }
126 Ok(Jid {
127 node: node,
128 domain: domain.ok_or(JidParseError::NoDomain)?,
129 resource: resource,
130 })
131 }
132}
133
134impl Jid {
135 /// Constructs a Jabber ID containing all three components.
136 ///
137 /// This is of the form `node`@`domain`/`resource`.
138 ///
139 /// # Examples
140 ///
141 /// ```
142 /// use jid::Jid;
143 ///
144 /// let jid = Jid::full("node", "domain", "resource");
145 ///
146 /// assert_eq!(jid.node, Some("node".to_owned()));
147 /// assert_eq!(jid.domain, "domain".to_owned());
148 /// assert_eq!(jid.resource, Some("resource".to_owned()));
149 /// ```
150 pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
151 where NS: Into<String>
152 , DS: Into<String>
153 , RS: Into<String> {
154 Jid {
155 node: Some(node.into()),
156 domain: domain.into(),
157 resource: Some(resource.into()),
158 }
159 }
160
161 /// Constructs a Jabber ID containing only the `node` and `domain` components.
162 ///
163 /// This is of the form `node`@`domain`.
164 ///
165 /// # Examples
166 ///
167 /// ```
168 /// use jid::Jid;
169 ///
170 /// let jid = Jid::bare("node", "domain");
171 ///
172 /// assert_eq!(jid.node, Some("node".to_owned()));
173 /// assert_eq!(jid.domain, "domain".to_owned());
174 /// assert_eq!(jid.resource, None);
175 /// ```
176 pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
177 where NS: Into<String>
178 , DS: Into<String> {
179 Jid {
180 node: Some(node.into()),
181 domain: domain.into(),
182 resource: None,
183 }
184 }
185
186 /// Constructs a Jabber ID containing only a `domain`.
187 ///
188 /// This is of the form `domain`.
189 ///
190 /// # Examples
191 ///
192 /// ```
193 /// use jid::Jid;
194 ///
195 /// let jid = Jid::domain("domain");
196 ///
197 /// assert_eq!(jid.node, None);
198 /// assert_eq!(jid.domain, "domain".to_owned());
199 /// assert_eq!(jid.resource, None);
200 /// ```
201 pub fn domain<DS>(domain: DS) -> Jid
202 where DS: Into<String> {
203 Jid {
204 node: None,
205 domain: domain.into(),
206 resource: None,
207 }
208 }
209
210 /// Constructs a Jabber ID containing the `domain` and `resource` components.
211 ///
212 /// This is of the form `domain`/`resource`.
213 ///
214 /// # Examples
215 ///
216 /// ```
217 /// use jid::Jid;
218 ///
219 /// let jid = Jid::domain_with_resource("domain", "resource");
220 ///
221 /// assert_eq!(jid.node, None);
222 /// assert_eq!(jid.domain, "domain".to_owned());
223 /// assert_eq!(jid.resource, Some("resource".to_owned()));
224 /// ```
225 pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
226 where DS: Into<String>
227 , RS: Into<String> {
228 Jid {
229 node: None,
230 domain: domain.into(),
231 resource: Some(resource.into()),
232 }
233 }
234
235 /// Constructs a new Jabber ID from an existing one, with the node swapped out with a new one.
236 ///
237 /// # Examples
238 ///
239 /// ```
240 /// use jid::Jid;
241 ///
242 /// let jid = Jid::domain("domain");
243 ///
244 /// assert_eq!(jid.node, None);
245 ///
246 /// let new_jid = jid.with_node("node");
247 ///
248 /// assert_eq!(new_jid.node, Some("node".to_owned()));
249 /// ```
250 pub fn with_node<S>(&self, node: S) -> Jid
251 where S: Into<String> {
252 Jid {
253 node: Some(node.into()),
254 domain: self.domain.clone(),
255 resource: self.resource.clone(),
256 }
257 }
258
259 /// Constructs a new Jabber ID from an existing one, with the domain swapped out with a new one.
260 ///
261 /// # Examples
262 ///
263 /// ```
264 /// use jid::Jid;
265 ///
266 /// let jid = Jid::domain("domain");
267 ///
268 /// assert_eq!(jid.domain, "domain");
269 ///
270 /// let new_jid = jid.with_domain("new_domain");
271 ///
272 /// assert_eq!(new_jid.domain, "new_domain");
273 /// ```
274 pub fn with_domain<S>(&self, domain: S) -> Jid
275 where S: Into<String> {
276 Jid {
277 node: self.node.clone(),
278 domain: domain.into(),
279 resource: self.resource.clone(),
280 }
281 }
282
283 /// Constructs a new Jabber ID from an existing one, with the resource swapped out with a new one.
284 ///
285 /// # Examples
286 ///
287 /// ```
288 /// use jid::Jid;
289 ///
290 /// let jid = Jid::domain("domain");
291 ///
292 /// assert_eq!(jid.resource, None);
293 ///
294 /// let new_jid = jid.with_resource("resource");
295 ///
296 /// assert_eq!(new_jid.resource, Some("resource".to_owned()));
297 /// ```
298 pub fn with_resource<S>(&self, resource: S) -> Jid
299 where S: Into<String> {
300 Jid {
301 node: self.node.clone(),
302 domain: self.domain.clone(),
303 resource: Some(resource.into()),
304 }
305 }
306
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 use std::str::FromStr;
314
315 #[test]
316 fn can_parse_jids() {
317 assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
318 assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
319 assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
320
321 assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
322
323 assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
324 }
325
326 #[test]
327 fn serialise() {
328 assert_eq!(String::from(Jid::full("a", "b", "c")), String::from("a@b/c"));
329 }
330}