use crate::transport::mqtt::str_topic::{StrTopic, StrTopicError};
use crate::transport::mqtt::topic::Topic;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct RoutedStrTopic<const ROUTE_LEVEL: u8 = 255> {
topic: StrTopic,
}
impl<const ROUTE_LEVEL: u8> RoutedStrTopic<ROUTE_LEVEL> {
pub fn route_level(self) -> u8 {
ROUTE_LEVEL
}
pub fn replace_at(&mut self, level: u8, value: &str) -> Result<(), StrTopicError> {
self.topic.replace_at(level, value)
}
}
impl<const ROUTE_LEVEL: u8> Default for RoutedStrTopic<ROUTE_LEVEL> {
fn default() -> Self {
Self {
topic: StrTopic::default(),
}
}
}
impl<const ROUTE_LEVEL: u8> Display for RoutedStrTopic<ROUTE_LEVEL> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.topic)
}
}
impl<const ROUTE_LEVEL: u8> FromStr for RoutedStrTopic<ROUTE_LEVEL> {
type Err = std::str::Utf8Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match StrTopic::from_str(s) {
Ok(topic) => Ok(RoutedStrTopic { topic }),
Err(e) => Err(e),
}
}
}
impl<const ROUTE_LEVEL: u8> Topic for RoutedStrTopic<ROUTE_LEVEL> {
fn as_route(&self) -> String {
if ROUTE_LEVEL == 0 {
String::new()
} else {
let string_topic = self.topic.to_string();
let parts = self.topic.parts();
if ROUTE_LEVEL as usize >= parts.len() {
string_topic
} else {
parts[..ROUTE_LEVEL as usize].join("/")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
fn create_routed_str_topic(topic: &str) -> RoutedStrTopic {
RoutedStrTopic {
topic: StrTopic::from_str(topic).unwrap(),
}
}
fn create_routed_str_topic_with_levels<const LEVEL: u8>(topic: &str) -> RoutedStrTopic<LEVEL> {
RoutedStrTopic {
topic: StrTopic::from_str(topic).unwrap(),
}
}
#[test]
fn routed_str_topic_display() {
let topic = create_routed_str_topic("test/topic");
assert_eq!(format!("{topic}"), "test/topic");
}
#[test]
fn routed_str_topic_from_str_valid() {
let topic = RoutedStrTopic::from_str("test/topic").unwrap();
assert_eq!(topic, create_routed_str_topic("test/topic"));
}
#[test]
fn routed_str_topic_from_str_empty() {
let topic = RoutedStrTopic::from_str("").unwrap();
assert_eq!(topic, create_routed_str_topic(""));
}
#[test]
fn routed_str_topic_from_str_with_route_level_3() {
let topic = RoutedStrTopic::<3>::from_str("a/b/c/d/e").unwrap();
assert_eq!(topic.as_route(), "a/b/c");
assert_eq!(topic.route_level(), 3);
}
#[test]
fn routed_str_topic_const_generic_default() {
let topic = RoutedStrTopic::<255>::default();
assert_eq!(topic.route_level(), 255);
}
#[test]
fn routed_str_topic_as_route() {
let topic = create_routed_str_topic("test/route");
assert_eq!(topic.as_route(), "test/route");
}
#[test]
fn routed_str_topic_as_route_with_none_levels() {
let topic = create_routed_str_topic_with_levels::<255>("a/b/c/d/e");
assert_eq!(topic.as_route(), "a/b/c/d/e");
}
#[test]
fn routed_str_topic_as_route_with_zero_levels() {
let topic = create_routed_str_topic_with_levels::<0>("a/b/c/d/e");
assert_eq!(topic.as_route(), "");
}
#[test]
fn routed_str_topic_as_route_with_one_level() {
let topic = create_routed_str_topic_with_levels::<1>("a/b/c/d/e");
assert_eq!(topic.as_route(), "a");
}
#[test]
fn routed_str_topic_as_route_with_three_levels() {
let topic = create_routed_str_topic_with_levels::<3>("a/b/c/d/e");
assert_eq!(topic.as_route(), "a/b/c");
}
#[test]
fn routed_str_topic_as_route_with_more_levels_than_available() {
let topic = create_routed_str_topic_with_levels::<10>("a/b/c");
assert_eq!(topic.as_route(), "a/b/c");
}
#[test]
fn routed_str_topic_as_route_single_level_topic() {
let topic = create_routed_str_topic_with_levels::<1>("single");
assert_eq!(topic.as_route(), "single");
}
#[test]
fn routed_str_topic_as_route_single_level_topic_zero_levels() {
let topic = create_routed_str_topic_with_levels::<0>("single");
assert_eq!(topic.as_route(), "");
}
#[test]
fn routed_str_topic_with_route_levels_constructor() {
let topic = create_routed_str_topic_with_levels::<2>("a/b/c/d");
assert_eq!(topic.to_string(), "a/b/c/d");
assert_eq!(topic.as_route(), "a/b");
}
#[test]
fn routed_str_topic_not_replace_at_at_level_0() {
let mut topic = create_routed_str_topic("a/b/c/d");
let result = topic.replace_at(0, "x");
assert!(result.is_err());
assert_eq!(result.unwrap_err(), StrTopicError::LevelZero);
assert_eq!(topic.to_string(), "a/b/c/d");
}
#[test]
fn routed_str_topic_replace_at_at_first_level() {
let mut topic = create_routed_str_topic("a/b/c/d");
let result = topic.replace_at(1, "x");
assert!(result.is_ok());
assert_eq!(topic.to_string(), "x/b/c/d");
}
#[test]
fn routed_str_topic_replace_at_at_level_2() {
let mut topic = create_routed_str_topic("a/b/c/d");
let result = topic.replace_at(2, "x");
assert!(result.is_ok());
assert_eq!(topic.to_string(), "a/x/c/d");
}
#[test]
fn routed_str_topic_replace_at_at_level_3() {
let mut topic = create_routed_str_topic("a/b/c/d");
let result = topic.replace_at(3, "x");
assert!(result.is_ok());
assert_eq!(topic.to_string(), "a/b/x/d");
}
#[test]
fn routed_str_topic_replace_at_at_last_level() {
let mut topic = create_routed_str_topic("a/b/c/d");
let result = topic.replace_at(4, "x");
assert!(result.is_ok());
assert_eq!(topic.to_string(), "a/b/c/x");
}
#[test]
fn routed_str_topic_not_replace_at_at_level_too_high() {
let mut topic = create_routed_str_topic("a/b/c/d");
let result = topic.replace_at(5, "x");
assert!(result.is_err());
assert_eq!(result.unwrap_err(), StrTopicError::LevelTooHigh(5));
assert_eq!(topic.to_string(), "a/b/c/d");
}
}