libits/transport/mqtt/
str_topic.rs1use crate::transport::mqtt::topic::Topic;
13use std::fmt::{Display, Formatter};
14use std::str::FromStr;
15use thiserror::Error;
16
17#[derive(Error, Debug, PartialEq)]
19pub enum StrTopicError {
20 #[error("Cannot update topic at level 0")]
21 LevelZero,
22 #[error("Cannot update topic at level {0}: level is too high")]
23 LevelTooHigh(u8),
24}
25
26#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)]
28pub struct StrTopic {
29 topic: String,
31}
32
33impl StrTopic {
34 pub fn parts(&self) -> Vec<&str> {
36 self.topic.split('/').collect()
37 }
38
39 pub fn replace_at(&mut self, level: u8, value: &str) -> Result<(), StrTopicError> {
41 if level == 0 {
42 return Err(StrTopicError::LevelZero);
43 }
44 let mut parts = self.parts();
45 if level as usize <= parts.len() {
46 parts[level as usize - 1] = value;
47 self.topic = parts.join("/");
48 Ok(())
49 } else {
50 Err(StrTopicError::LevelTooHigh(level))
51 }
52 }
53}
54
55impl Display for StrTopic {
56 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
66 write!(f, "{}", self.topic)
67 }
68}
69
70impl FromStr for StrTopic {
71 type Err = std::str::Utf8Error;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
83 Ok(StrTopic {
84 topic: String::from(s),
85 })
86 }
87}
88
89impl Topic for StrTopic {
90 fn as_route(&self) -> String {
96 self.topic.clone()
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use std::str::FromStr;
106
107 fn create_str_topic(topic: &str) -> StrTopic {
117 StrTopic {
118 topic: topic.to_string(),
119 }
120 }
121
122 #[test]
123 fn str_topic_display() {
124 let topic = create_str_topic("test/topic");
125 assert_eq!(format!("{topic}"), "test/topic");
126 }
127
128 #[test]
129 fn str_topic_from_str_valid() {
130 let topic = StrTopic::from_str("test/topic").unwrap();
131 assert_eq!(topic, create_str_topic("test/topic"));
132 }
133
134 #[test]
135 fn str_topic_from_str_empty() {
136 let topic = StrTopic::from_str("").unwrap();
137 assert_eq!(topic, create_str_topic(""));
138 }
139
140 #[test]
141 fn str_topic_as_route() {
142 let topic = create_str_topic("test/route");
143 assert_eq!(topic.as_route(), "test/route");
144 }
145
146 #[test]
147 fn str_topic_not_replace_at_at_level_0() {
148 let mut topic = create_str_topic("a/b/c/d");
149 let result = topic.replace_at(0, "x");
150 assert!(result.is_err());
151 assert_eq!(result.unwrap_err(), StrTopicError::LevelZero);
152 assert_eq!(topic.to_string(), "a/b/c/d");
153 }
154
155 #[test]
156 fn str_topic_replace_at_at_first_level() {
157 let mut topic = create_str_topic("a/b/c/d");
158 let result = topic.replace_at(1, "x");
159 assert!(result.is_ok());
160 assert_eq!(topic.to_string(), "x/b/c/d");
161 }
162
163 #[test]
164 fn str_topic_replace_at_at_level_2() {
165 let mut topic = create_str_topic("a/b/c/d");
166 let result = topic.replace_at(2, "x");
167 assert!(result.is_ok());
168 assert_eq!(topic.to_string(), "a/x/c/d");
169 }
170
171 #[test]
172 fn str_topic_replace_at_at_level_3() {
173 let mut topic = create_str_topic("a/b/c/d");
174 let result = topic.replace_at(3, "x");
175 assert!(result.is_ok());
176 assert_eq!(topic.to_string(), "a/b/x/d");
177 }
178
179 #[test]
180 fn str_topic_replace_at_at_last_level() {
181 let mut topic = create_str_topic("a/b/c/d");
182 let result = topic.replace_at(4, "x");
183 assert!(result.is_ok());
184 assert_eq!(topic.to_string(), "a/b/c/x");
185 }
186
187 #[test]
188 fn str_topic_not_replace_at_at_level_too_high() {
189 let mut topic = create_str_topic("a/b/c/d");
190 let result = topic.replace_at(5, "x");
191 assert!(result.is_err());
192 assert_eq!(result.unwrap_err(), StrTopicError::LevelTooHigh(5));
193 assert_eq!(topic.to_string(), "a/b/c/d");
194 }
195}