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