1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
use std::io::{Read, Write}; use std::convert::Into; use std::ops::Deref; use std::fmt; use std::error::Error; use regex::Regex; use encodable::StringEncodeError; use {Encodable, Decodable}; const TOPIC_NAME_VALIDATE_REGEX: &'static str = r"^(\$?[^/\$]+)?(/[^/\$]+)*$"; #[derive(Debug, Eq, PartialEq, Clone)] pub struct TopicName(String); impl TopicName { pub fn new(topic_name: String) -> Result<TopicName, TopicNameError> { let re = Regex::new(TOPIC_NAME_VALIDATE_REGEX).unwrap(); if topic_name.is_empty() || topic_name.as_bytes().len() > 65535 || !re.is_match(&topic_name[..]) { Err(TopicNameError::InvalidTopicName(topic_name)) } else { Ok(TopicName(topic_name)) } } pub unsafe fn new_unchecked(topic_name: String) -> TopicName { TopicName(topic_name) } pub fn is_server_specific(&self) -> bool { self.0.starts_with('$') } } impl Into<String> for TopicName { fn into(self) -> String { self.0 } } impl Deref for TopicName { type Target = String; fn deref(&self) -> &String { &self.0 } } pub struct TopicNameRef<'a>(&'a str); impl<'a> TopicNameRef<'a> { pub fn is_server_specific(&self) -> bool { self.0.starts_with('$') } } impl<'a> Encodable<'a> for TopicName { type Err = TopicNameError; fn encode<W: Write>(&self, writer: &mut W) -> Result<(), TopicNameError> { (&self.0[..]).encode(writer).map_err(TopicNameError::StringEncodeError) } fn encoded_length(&self) -> u32 { (&self.0[..]).encoded_length() } } impl<'a> Decodable<'a> for TopicName { type Err = TopicNameError; type Cond = (); fn decode_with<R: Read>(reader: &mut R, _rest: Option<()>) -> Result<TopicName, TopicNameError> { TopicName::new(try!(Decodable::decode(reader).map_err(TopicNameError::StringEncodeError))) } } #[derive(Debug)] pub enum TopicNameError { StringEncodeError(StringEncodeError), InvalidTopicName(String), } impl fmt::Display for TopicNameError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { &TopicNameError::StringEncodeError(ref err) => err.fmt(f), &TopicNameError::InvalidTopicName(ref topic) => write!(f, "Invalid topic filter ({})", topic), } } } impl Error for TopicNameError { fn description(&self) -> &str { match self { &TopicNameError::StringEncodeError(ref err) => err.description(), &TopicNameError::InvalidTopicName(..) => "Invalid topic filter", } } fn cause(&self) -> Option<&Error> { match self { &TopicNameError::StringEncodeError(ref err) => Some(err), &TopicNameError::InvalidTopicName(..) => None, } } } #[cfg(test)] mod test { use super::*; #[test] fn test_topic_name_basic() { let topic_name = "$SYS".to_owned(); TopicName::new(topic_name).unwrap(); let topic_name = "$SYS/broker/connection/test.cosm-energy/state".to_owned(); TopicName::new(topic_name).unwrap(); } }