mqtt_codec/
topic.rs

1use std::fmt::{self, Write};
2use std::{io, ops, str::FromStr};
3
4use crate::error::TopicError;
5
6#[inline]
7fn is_metadata<T: AsRef<str>>(s: T) -> bool {
8    s.as_ref().chars().nth(0) == Some('$')
9}
10
11#[derive(Debug, Eq, PartialEq, Clone, Hash)]
12pub enum Level {
13    Normal(String),
14    Metadata(String), // $SYS
15    Blank,
16    SingleWildcard, // Single level wildcard +
17    MultiWildcard,  // Multi-level wildcard #
18}
19
20impl Level {
21    pub fn parse<T: AsRef<str>>(s: T) -> Result<Level, TopicError> {
22        Level::from_str(s.as_ref())
23    }
24
25    pub fn normal<T: AsRef<str>>(s: T) -> Level {
26        if s.as_ref().contains(|c| c == '+' || c == '#') {
27            panic!("invalid normal level `{}` contains +|#", s.as_ref());
28        }
29
30        if s.as_ref().chars().nth(0) == Some('$') {
31            panic!("invalid normal level `{}` starts with $", s.as_ref())
32        }
33
34        Level::Normal(String::from(s.as_ref()))
35    }
36
37    pub fn metadata<T: AsRef<str>>(s: T) -> Level {
38        if s.as_ref().contains(|c| c == '+' || c == '#') {
39            panic!("invalid metadata level `{}` contains +|#", s.as_ref());
40        }
41
42        if s.as_ref().chars().nth(0) != Some('$') {
43            panic!("invalid metadata level `{}` not starts with $", s.as_ref())
44        }
45
46        Level::Metadata(String::from(s.as_ref()))
47    }
48
49    #[inline]
50    pub fn value(&self) -> Option<&str> {
51        match *self {
52            Level::Normal(ref s) | Level::Metadata(ref s) => Some(s),
53            _ => None,
54        }
55    }
56
57    #[inline]
58    pub fn is_normal(&self) -> bool {
59        if let Level::Normal(_) = *self {
60            true
61        } else {
62            false
63        }
64    }
65
66    #[inline]
67    pub fn is_metadata(&self) -> bool {
68        if let Level::Metadata(_) = *self {
69            true
70        } else {
71            false
72        }
73    }
74
75    #[inline]
76    pub fn is_valid(&self) -> bool {
77        match *self {
78            Level::Normal(ref s) => {
79                s.chars().nth(0) != Some('$') && !s.contains(|c| c == '+' || c == '#')
80            }
81            Level::Metadata(ref s) => {
82                s.chars().nth(0) == Some('$') && !s.contains(|c| c == '+' || c == '#')
83            }
84            _ => true,
85        }
86    }
87}
88
89#[derive(Debug, Eq, Clone, Hash)]
90pub struct Topic(Vec<Level>);
91
92impl Topic {
93    #[inline]
94    pub fn levels(&self) -> &Vec<Level> {
95        &self.0
96    }
97
98    #[inline]
99    pub fn is_valid(&self) -> bool {
100        self.0
101            .iter()
102            .position(|level| !level.is_valid())
103            .or_else(|| {
104                self.0
105                    .iter()
106                    .enumerate()
107                    .position(|(pos, level)| match *level {
108                        Level::MultiWildcard => pos != self.0.len() - 1,
109                        Level::Metadata(_) => pos != 0,
110                        _ => false,
111                    })
112            })
113            .is_none()
114    }
115}
116
117macro_rules! match_topic {
118    ($topic:expr, $levels:expr) => {{
119        let mut lhs = $topic.0.iter();
120
121        for rhs in $levels {
122            match lhs.next() {
123                Some(&Level::SingleWildcard) => {
124                    if !rhs.match_level(&Level::SingleWildcard) {
125                        break;
126                    }
127                }
128                Some(&Level::MultiWildcard) => {
129                    return rhs.match_level(&Level::MultiWildcard);
130                }
131                Some(level) if rhs.match_level(level) => continue,
132                _ => return false,
133            }
134        }
135
136        match lhs.next() {
137            Some(&Level::MultiWildcard) => true,
138            Some(_) => false,
139            None => true,
140        }
141    }};
142}
143
144impl PartialEq for Topic {
145    fn eq(&self, other: &Topic) -> bool {
146        match_topic!(self, &other.0)
147    }
148}
149
150impl<T: AsRef<str>> PartialEq<T> for Topic {
151    fn eq(&self, other: &T) -> bool {
152        match_topic!(self, other.as_ref().split('/'))
153    }
154}
155
156impl<'a> From<&'a [Level]> for Topic {
157    fn from(s: &[Level]) -> Self {
158        let mut v = vec![];
159
160        v.extend_from_slice(s);
161
162        Topic(v)
163    }
164}
165
166impl From<Vec<Level>> for Topic {
167    fn from(v: Vec<Level>) -> Self {
168        Topic(v)
169    }
170}
171
172impl Into<Vec<Level>> for Topic {
173    fn into(self) -> Vec<Level> {
174        self.0
175    }
176}
177
178impl ops::Deref for Topic {
179    type Target = Vec<Level>;
180
181    fn deref(&self) -> &Self::Target {
182        &self.0
183    }
184}
185
186impl ops::DerefMut for Topic {
187    fn deref_mut(&mut self) -> &mut Self::Target {
188        &mut self.0
189    }
190}
191
192#[macro_export]
193macro_rules! topic {
194    ($s:expr) => {
195        $s.parse::<Topic>().unwrap()
196    };
197}
198
199pub trait MatchLevel {
200    fn match_level(&self, level: &Level) -> bool;
201}
202
203impl MatchLevel for Level {
204    fn match_level(&self, level: &Level) -> bool {
205        match *level {
206            Level::Normal(ref lhs) => {
207                if let Level::Normal(ref rhs) = *self {
208                    lhs == rhs
209                } else {
210                    false
211                }
212            }
213            Level::Metadata(ref lhs) => {
214                if let Level::Metadata(ref rhs) = *self {
215                    lhs == rhs
216                } else {
217                    false
218                }
219            }
220            Level::Blank => true,
221            Level::SingleWildcard | Level::MultiWildcard => !self.is_metadata(),
222        }
223    }
224}
225
226impl<T: AsRef<str>> MatchLevel for T {
227    fn match_level(&self, level: &Level) -> bool {
228        match *level {
229            Level::Normal(ref lhs) => !is_metadata(self) && lhs == self.as_ref(),
230            Level::Metadata(ref lhs) => is_metadata(self) && lhs == self.as_ref(),
231            Level::Blank => self.as_ref().is_empty(),
232            Level::SingleWildcard | Level::MultiWildcard => !is_metadata(self),
233        }
234    }
235}
236
237impl FromStr for Level {
238    type Err = TopicError;
239
240    #[inline]
241    fn from_str(s: &str) -> Result<Self, TopicError> {
242        match s {
243            "+" => Ok(Level::SingleWildcard),
244            "#" => Ok(Level::MultiWildcard),
245            "" => Ok(Level::Blank),
246            _ => {
247                if s.contains(|c| c == '+' || c == '#') {
248                    Err(TopicError::InvalidLevel)
249                } else if is_metadata(s) {
250                    Ok(Level::Metadata(String::from(s)))
251                } else {
252                    Ok(Level::Normal(String::from(s)))
253                }
254            }
255        }
256    }
257}
258
259impl FromStr for Topic {
260    type Err = TopicError;
261
262    #[inline]
263    fn from_str(s: &str) -> Result<Self, TopicError> {
264        s.split('/')
265            .map(|level| Level::from_str(level))
266            .collect::<Result<Vec<_>, TopicError>>()
267            .map(Topic)
268            .and_then(|topic| {
269                if topic.is_valid() {
270                    Ok(topic)
271                } else {
272                    Err(TopicError::InvalidTopic)
273                }
274            })
275    }
276}
277
278impl fmt::Display for Level {
279    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
280        match *self {
281            Level::Normal(ref s) | Level::Metadata(ref s) => f.write_str(s.as_str()),
282            Level::Blank => Ok(()),
283            Level::SingleWildcard => f.write_char('+'),
284            Level::MultiWildcard => f.write_char('#'),
285        }
286    }
287}
288
289impl fmt::Display for Topic {
290    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291        let mut first = true;
292
293        for level in &self.0 {
294            if first {
295                first = false;
296            } else {
297                f.write_char('/')?;
298            }
299
300            level.fmt(f)?;
301        }
302
303        Ok(())
304    }
305}
306
307pub trait WriteTopicExt: io::Write {
308    fn write_level(&mut self, level: &Level) -> io::Result<usize> {
309        match *level {
310            Level::Normal(ref s) | Level::Metadata(ref s) => self.write(s.as_str().as_bytes()),
311            Level::Blank => Ok(0),
312            Level::SingleWildcard => self.write(b"+"),
313            Level::MultiWildcard => self.write(b"#"),
314        }
315    }
316
317    fn write_topic(&mut self, topic: &Topic) -> io::Result<usize> {
318        let mut n = 0;
319        let mut first = true;
320
321        for level in topic.levels() {
322            if first {
323                first = false;
324            } else {
325                n += self.write(b"/")?;
326            }
327
328            n += self.write_level(level)?;
329        }
330
331        Ok(n)
332    }
333}
334
335impl<W: io::Write + ?Sized> WriteTopicExt for W {}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn test_level() {
343        assert!(Level::normal("sport").is_normal());
344        assert!(Level::metadata("$SYS").is_metadata());
345
346        assert_eq!(Level::normal("sport").value(), Some("sport"));
347        assert_eq!(Level::metadata("$SYS").value(), Some("$SYS"));
348
349        assert_eq!(Level::normal("sport"), "sport".parse().unwrap());
350        assert_eq!(Level::metadata("$SYS"), "$SYS".parse().unwrap());
351
352        assert!(Level::Normal(String::from("sport")).is_valid());
353        assert!(Level::Metadata(String::from("$SYS")).is_valid());
354
355        assert!(!Level::Normal(String::from("$sport")).is_valid());
356        assert!(!Level::Metadata(String::from("SYS")).is_valid());
357
358        assert!(!Level::Normal(String::from("sport#")).is_valid());
359        assert!(!Level::Metadata(String::from("SYS+")).is_valid());
360    }
361
362    #[test]
363    fn test_valid_topic() {
364        assert!(Topic(vec![
365            Level::normal("sport"),
366            Level::normal("tennis"),
367            Level::normal("player1")
368        ])
369        .is_valid());
370
371        assert!(Topic(vec![
372            Level::normal("sport"),
373            Level::normal("tennis"),
374            Level::MultiWildcard
375        ])
376        .is_valid());
377        assert!(Topic(vec![
378            Level::metadata("$SYS"),
379            Level::normal("tennis"),
380            Level::MultiWildcard
381        ])
382        .is_valid());
383
384        assert!(Topic(vec![
385            Level::normal("sport"),
386            Level::SingleWildcard,
387            Level::normal("player1")
388        ])
389        .is_valid());
390
391        assert!(!Topic(vec![
392            Level::normal("sport"),
393            Level::MultiWildcard,
394            Level::normal("player1")
395        ])
396        .is_valid());
397        assert!(!Topic(vec![
398            Level::normal("sport"),
399            Level::metadata("$SYS"),
400            Level::normal("player1")
401        ])
402        .is_valid());
403    }
404
405    #[test]
406    fn test_parse_topic() {
407        assert_eq!(
408            topic!("sport/tennis/player1"),
409            Topic::from(vec![
410                Level::normal("sport"),
411                Level::normal("tennis"),
412                Level::normal("player1")
413            ])
414        );
415
416        assert_eq!(topic!(""), Topic(vec![Level::Blank]));
417        assert_eq!(
418            topic!("/finance"),
419            Topic::from(vec![Level::Blank, Level::normal("finance")])
420        );
421
422        assert_eq!(topic!("$SYS"), Topic::from(vec![Level::metadata("$SYS")]));
423        assert!("sport/$SYS".parse::<Topic>().is_err());
424    }
425
426    #[test]
427    fn test_multi_wildcard_topic() {
428        assert_eq!(
429            topic!("sport/tennis/#"),
430            Topic::from(vec![
431                Level::normal("sport"),
432                Level::normal("tennis"),
433                Level::MultiWildcard
434            ])
435        );
436
437        assert_eq!(topic!("#"), Topic::from(vec![Level::MultiWildcard]));
438        assert!("sport/tennis#".parse::<Topic>().is_err());
439        assert!("sport/tennis/#/ranking".parse::<Topic>().is_err());
440    }
441
442    #[test]
443    fn test_single_wildcard_topic() {
444        assert_eq!(topic!("+"), Topic::from(vec![Level::SingleWildcard]));
445
446        assert_eq!(
447            topic!("+/tennis/#"),
448            Topic::from(vec![
449                Level::SingleWildcard,
450                Level::normal("tennis"),
451                Level::MultiWildcard
452            ])
453        );
454
455        assert_eq!(
456            topic!("sport/+/player1"),
457            Topic::from(vec![
458                Level::normal("sport"),
459                Level::SingleWildcard,
460                Level::normal("player1")
461            ])
462        );
463
464        assert!("sport+".parse::<Topic>().is_err());
465    }
466
467    #[test]
468    fn test_write_topic() {
469        let mut v = vec![];
470        let t = vec![
471            Level::SingleWildcard,
472            Level::normal("tennis"),
473            Level::MultiWildcard,
474        ]
475        .into();
476
477        assert_eq!(v.write_topic(&t).unwrap(), 10);
478        assert_eq!(v, b"+/tennis/#");
479
480        assert_eq!(format!("{}", t), "+/tennis/#");
481        assert_eq!(t.to_string(), "+/tennis/#");
482    }
483
484    #[test]
485    fn test_match_topic() {
486        assert!("test".match_level(&Level::normal("test")));
487        assert!("$SYS".match_level(&Level::metadata("$SYS")));
488
489        let t: Topic = "sport/tennis/player1/#".parse().unwrap();
490
491        assert_eq!(t, "sport/tennis/player1");
492        assert_eq!(t, "sport/tennis/player1/ranking");
493        assert_eq!(t, "sport/tennis/player1/score/wimbledon");
494
495        assert_eq!(Topic::from_str("sport/#").unwrap(), "sport");
496
497        let t: Topic = "sport/tennis/+".parse().unwrap();
498
499        assert_eq!(t, "sport/tennis/player1");
500        assert_eq!(t, "sport/tennis/player2");
501        assert!(t != "sport/tennis/player1/ranking");
502
503        let t: Topic = "sport/+".parse().unwrap();
504
505        assert!(t != "sport");
506        assert_eq!(t, "sport/");
507
508        assert_eq!(Topic::from_str("+/+").unwrap(), "/finance");
509        assert_eq!(Topic::from_str("/+").unwrap(), "/finance",);
510        assert!(Topic::from_str("+").unwrap() != "/finance",);
511
512        assert!(Topic::from_str("#").unwrap() != "$SYS");
513        assert!(Topic::from_str("+/monitor/Clients").unwrap() != "$SYS/monitor/Clients");
514        assert_eq!(Topic::from_str(&"$SYS/#").unwrap(), "$SYS/");
515        assert_eq!(
516            Topic::from_str("$SYS/monitor/+").unwrap(),
517            "$SYS/monitor/Clients",
518        );
519    }
520}