Skip to content

Commit

Permalink
Add parsing code for the Topic type
Browse files Browse the repository at this point in the history
  • Loading branch information
bschwind committed Jan 24, 2020
1 parent 1b4b67e commit 61ed4d7
Showing 1 changed file with 86 additions and 2 deletions.
88 changes: 86 additions & 2 deletions src/topic/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub enum TopicFilter {

/// A topic name publishers use when sending MQTT messages.
/// Cannot contain wildcards.
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub struct Topic {
topic_name: String,
level_count: u32,
Expand All @@ -37,6 +37,7 @@ pub enum TopicParseError {
InvalidWildcardLevel,
InvalidSharedGroupName,
EmptySharedGroupName,
WildcardOrNullInTopic,
}

/// If Ok, returns (level_count, contains_wildcards).
Expand Down Expand Up @@ -70,6 +71,11 @@ impl FromStr for TopicFilter {
return Err(TopicParseError::EmptyTopic);
}

// TODO - assert no null character U+0000
if filter.contains('\0') {
return Err(TopicParseError::WildcardOrNullInTopic);
}

// Filters cannot exceed the byte length in the MQTT spec
if filter.len() > MAX_TOPIC_LEN_BYTES {
return Err(TopicParseError::TopicTooLong);
Expand Down Expand Up @@ -148,6 +154,37 @@ impl FromStr for TopicFilter {
}
}

impl FromStr for Topic {
type Err = TopicParseError;

fn from_str(topic: &str) -> Result<Self, Self::Err> {
// TODO - Consider disallowing leading $ characters

// Topics cannot be empty
if topic.is_empty() {
return Err(TopicParseError::EmptyTopic);
}

// Topics cannot exceed the byte length in the MQTT spec
if topic.len() > MAX_TOPIC_LEN_BYTES {
return Err(TopicParseError::TopicTooLong);
}

// Topics cannot contain wildcards or null characters
if topic.contains(|x: char| {
x == SINGLE_LEVEL_WILDCARD || x == MULTI_LEVEL_WILDCARD || x == '\0'
}) {
return Err(TopicParseError::WildcardOrNullInTopic);
}

let level_count = topic.split(TOPIC_SEPARATOR).count() as u32;

let topic = Topic { topic_name: topic.to_string(), level_count };

Ok(topic)
}
}

pub struct TopicLevels<'a> {
levels_iter: std::str::Split<'a, char>,
}
Expand Down Expand Up @@ -182,7 +219,7 @@ impl<'a> Iterator for TopicLevels<'a> {

#[cfg(test)]
mod tests {
use crate::topic::{TopicFilter, TopicLevel, TopicParseError, MAX_TOPIC_LEN_BYTES};
use crate::topic::{Topic, TopicFilter, TopicLevel, TopicParseError, MAX_TOPIC_LEN_BYTES};

#[test]
fn test_topic_filter_parse_empty_topic() {
Expand Down Expand Up @@ -429,6 +466,53 @@ mod tests {
);
}

#[test]
fn test_topic_name_success() {
assert_eq!(
"/".parse::<Topic>().unwrap(),
Topic { topic_name: "/".to_string(), level_count: 2 }
);

assert_eq!(
"Accounts payable".parse::<Topic>().unwrap(),
Topic { topic_name: "Accounts payable".to_string(), level_count: 1 }
);

assert_eq!(
"home/kitchen".parse::<Topic>().unwrap(),
Topic { topic_name: "home/kitchen".to_string(), level_count: 2 }
);

assert_eq!(
"home/kitchen/temperature".parse::<Topic>().unwrap(),
Topic { topic_name: "home/kitchen/temperature".to_string(), level_count: 3 }
);
}

#[test]
fn test_topic_name_failure() {
assert_eq!("#".parse::<Topic>().unwrap_err(), TopicParseError::WildcardOrNullInTopic,);

assert_eq!("+".parse::<Topic>().unwrap_err(), TopicParseError::WildcardOrNullInTopic,);

assert_eq!("\0".parse::<Topic>().unwrap_err(), TopicParseError::WildcardOrNullInTopic,);

assert_eq!(
"/multi/level/#".parse::<Topic>().unwrap_err(),
TopicParseError::WildcardOrNullInTopic,
);

assert_eq!(
"/single/level/+".parse::<Topic>().unwrap_err(),
TopicParseError::WildcardOrNullInTopic,
);

assert_eq!(
"/null/byte/\0".parse::<Topic>().unwrap_err(),
TopicParseError::WildcardOrNullInTopic,
);
}

#[test]
fn test_topic_filter_level_iterator_simple() {
let filter: TopicFilter = "/".parse().unwrap();
Expand Down

0 comments on commit 61ed4d7

Please sign in to comment.