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}