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), Blank,
16 SingleWildcard, MultiWildcard, }
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}