RustMqtt/
topic.rs

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/// FIXME: replace String with &str
9#[derive(Debug, Clone, PartialEq)]
10pub enum Topic {
11    Normal(String),
12    System(String), // $SYS = Topic::System("$SYS")
13    Blank,
14    SingleWildcard, // +
15    MultiWildcard,  // #
16}
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    // Should be false for Topic Name
68    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        // check for wildcards
127        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}