1use crate::{Error, Result};
2use std::vec::IntoIter;
3
4const TOPIC_PATH_DELIMITER: char = '/';
5
6use self::Topic::{Blank, MultiWildcard, Normal, SingleWildcard, System};
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum Topic {
11 Normal(String),
12 System(String), Blank,
14 SingleWildcard, MultiWildcard, }
17
18impl Into<String> for Topic {
19 fn into(self) -> String {
20 match self {
21 Normal(s) | System(s) => s,
22 Blank => "".to_string(),
23 SingleWildcard => "+".to_string(),
24 MultiWildcard => "#".to_string(),
25 }
26 }
27}
28
29impl Topic {
30 pub fn validate(topic: &str) -> bool {
31 match topic {
32 "+" | "#" => true,
33 _ => !(topic.contains("+") || topic.contains("#")),
34 }
35 }
36
37 pub fn fit(&self, other: &Topic) -> bool {
38 match *self {
39 Normal(ref str) => match *other {
40 Normal(ref s) => str == s,
41 SingleWildcard | MultiWildcard => true,
42 _ => false,
43 },
44 System(ref str) => match *other {
45 System(ref s) => str == s,
46 _ => false,
47 },
48 Blank => match *other {
49 Blank | SingleWildcard | MultiWildcard => true,
50 _ => false,
51 },
52 SingleWildcard => match *other {
53 System(_) => false,
54 _ => true,
55 },
56 MultiWildcard => match *other {
57 System(_) => false,
58 _ => true,
59 },
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
65pub struct TopicPath {
66 pub path: String,
67 pub wildcards: bool,
69 topics: Vec<Topic>,
70}
71
72impl TopicPath {
73 pub fn path(&self) -> String {
74 self.path.clone()
75 }
76
77 pub fn get(&self, index: usize) -> Option<&Topic> {
78 self.topics.get(index)
79 }
80
81 pub fn get_mut(&mut self, index: usize) -> Option<&mut Topic> {
82 self.topics.get_mut(index)
83 }
84
85 pub fn len(&self) -> usize {
86 self.topics.len()
87 }
88
89 pub fn is_final(&self, index: usize) -> bool {
90 let len = self.topics.len();
91 len == 0 || len - 1 == index
92 }
93
94 pub fn is_multi(&self, index: usize) -> bool {
95 match self.topics.get(index) {
96 Some(topic) => *topic == Topic::MultiWildcard,
97 None => false,
98 }
99 }
100
101 pub fn from_str<T: AsRef<str>>(path: T) -> Result<TopicPath> {
102 let mut valid = true;
103 let topics: Vec<Topic> = path
104 .as_ref()
105 .split(TOPIC_PATH_DELIMITER)
106 .map(|topic| match topic {
107 "+" => Topic::SingleWildcard,
108 "#" => Topic::MultiWildcard,
109 "" => Topic::Blank,
110 _ => {
111 if !Topic::validate(topic) {
112 valid = false;
113 }
114 if topic.chars().nth(0) == Some('$') {
115 Topic::System(String::from(topic))
116 } else {
117 Topic::Normal(String::from(topic))
118 }
119 }
120 })
121 .collect();
122
123 if !valid {
124 return Err(Error::InvalidTopicPath);
125 }
126 let wildcards = topics.iter().any(|topic| match *topic {
128 Topic::SingleWildcard | Topic::MultiWildcard => true,
129 _ => false,
130 });
131
132 Ok(TopicPath {
133 path: String::from(path.as_ref()),
134 topics: topics,
135 wildcards: wildcards,
136 })
137 }
138}
139
140impl IntoIterator for TopicPath {
141 type Item = Topic;
142 type IntoIter = IntoIter<Topic>;
143 fn into_iter(self) -> Self::IntoIter {
144 self.topics.into_iter()
145 }
146}
147
148impl<'a> From<&'a str> for TopicPath {
149 fn from(str: &'a str) -> TopicPath {
150 Self::from_str(str).unwrap()
151 }
152}
153
154impl From<String> for TopicPath {
155 fn from(path: String) -> TopicPath {
156 Self::from_str(path).unwrap()
157 }
158}
159
160impl Into<String> for TopicPath {
161 fn into(self) -> String {
162 self.path
163 }
164}
165
166pub trait ToTopicPath {
167 fn to_topic_path(&self) -> Result<TopicPath>;
168
169 fn to_topic_name(&self) -> Result<TopicPath> {
170 let topic_name = self.to_topic_path()?;
171 match topic_name.wildcards {
172 false => Ok(topic_name),
173 true => Err(Error::TopicNameMustNotContainWildcard),
174 }
175 }
176}
177
178impl ToTopicPath for TopicPath {
179 fn to_topic_path(&self) -> Result<TopicPath> {
180 Ok(self.clone())
181 }
182}
183
184impl ToTopicPath for String {
185 fn to_topic_path(&self) -> Result<TopicPath> {
186 TopicPath::from_str(self.clone())
187 }
188}
189
190impl<'a> ToTopicPath for &'a str {
191 fn to_topic_path(&self) -> Result<TopicPath> {
192 TopicPath::from_str(*self)
193 }
194}
195
196#[cfg(test)]
197mod test {
198 use super::{Topic, TopicPath};
199
200 #[test]
201 fn topic_path_test() {
202 let path = "/$SYS/test/+/#";
203 let topic = TopicPath::from(path);
204 let mut iter = topic.into_iter();
205 assert_eq!(iter.next().unwrap(), Topic::Blank);
206 assert_eq!(iter.next().unwrap(), Topic::System("$SYS".to_string()));
207 assert_eq!(iter.next().unwrap(), Topic::Normal("test".to_string()));
208 assert_eq!(iter.next().unwrap(), Topic::SingleWildcard);
209 assert_eq!(iter.next().unwrap(), Topic::MultiWildcard);
210 }
211
212 #[test]
213 fn wildcards_test() {
214 let topic = TopicPath::from("/a/b/c");
215 assert!(!topic.wildcards);
216 let topic = TopicPath::from("/a/+/c");
217 assert!(topic.wildcards);
218 let topic = TopicPath::from("/a/b/#");
219 assert!(topic.wildcards);
220 }
221
222 #[test]
223 fn topic_is_not_valid_test() {
224 assert!(TopicPath::from_str("+wrong").is_err());
225 assert!(TopicPath::from_str("wro#ng").is_err());
226 assert!(TopicPath::from_str("w/r/o/n/g+").is_err());
227 }
228}