libits/transport/mqtt/
routed_str_topic.rs1use crate::transport::mqtt::str_topic::{StrTopic, StrTopicError};
13use crate::transport::mqtt::topic::Topic;
14use std::fmt::{Display, Formatter};
15use std::str::FromStr;
16
17#[derive(Clone, Debug, Hash, PartialEq, Eq)]
23pub struct RoutedStrTopic<const ROUTE_LEVEL: u8 = 255> {
24 topic: StrTopic,
26}
27
28impl<const ROUTE_LEVEL: u8> RoutedStrTopic<ROUTE_LEVEL> {
29 pub fn route_level(self) -> u8 {
31 ROUTE_LEVEL
32 }
33
34 pub fn replace_at(&mut self, level: u8, value: &str) -> Result<(), StrTopicError> {
36 self.topic.replace_at(level, value)
37 }
38}
39
40impl<const ROUTE_LEVEL: u8> Default for RoutedStrTopic<ROUTE_LEVEL> {
41 fn default() -> Self {
42 Self {
43 topic: StrTopic::default(),
44 }
45 }
46}
47
48impl<const ROUTE_LEVEL: u8> Display for RoutedStrTopic<ROUTE_LEVEL> {
49 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59 write!(f, "{}", self.topic)
60 }
61}
62
63impl<const ROUTE_LEVEL: u8> FromStr for RoutedStrTopic<ROUTE_LEVEL> {
64 type Err = std::str::Utf8Error;
65
66 fn from_str(s: &str) -> Result<Self, Self::Err> {
76 match StrTopic::from_str(s) {
77 Ok(topic) => Ok(RoutedStrTopic { topic }),
78 Err(e) => Err(e),
79 }
80 }
81}
82
83impl<const ROUTE_LEVEL: u8> Topic for RoutedStrTopic<ROUTE_LEVEL> {
84 fn as_route(&self) -> String {
90 if ROUTE_LEVEL == 0 {
91 String::new()
93 } else {
94 let string_topic = self.topic.to_string();
95 let parts = self.topic.parts();
97 if ROUTE_LEVEL as usize >= parts.len() {
98 string_topic
99 } else {
100 parts[..ROUTE_LEVEL as usize].join("/")
101 }
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use std::str::FromStr;
110
111 fn create_routed_str_topic(topic: &str) -> RoutedStrTopic {
121 RoutedStrTopic {
122 topic: StrTopic::from_str(topic).unwrap(),
123 }
124 }
125
126 fn create_routed_str_topic_with_levels<const LEVEL: u8>(topic: &str) -> RoutedStrTopic<LEVEL> {
127 RoutedStrTopic {
128 topic: StrTopic::from_str(topic).unwrap(),
129 }
130 }
131
132 #[test]
133 fn routed_str_topic_display() {
134 let topic = create_routed_str_topic("test/topic");
135 assert_eq!(format!("{topic}"), "test/topic");
136 }
137
138 #[test]
139 fn routed_str_topic_from_str_valid() {
140 let topic = RoutedStrTopic::from_str("test/topic").unwrap();
141 assert_eq!(topic, create_routed_str_topic("test/topic"));
142 }
143
144 #[test]
145 fn routed_str_topic_from_str_empty() {
146 let topic = RoutedStrTopic::from_str("").unwrap();
147 assert_eq!(topic, create_routed_str_topic(""));
148 }
149
150 #[test]
151 fn routed_str_topic_from_str_with_route_level_3() {
152 let topic = RoutedStrTopic::<3>::from_str("a/b/c/d/e").unwrap();
153 assert_eq!(topic.as_route(), "a/b/c");
154 assert_eq!(topic.route_level(), 3);
155 }
156
157 #[test]
158 fn routed_str_topic_const_generic_default() {
159 let topic = RoutedStrTopic::<255>::default();
160 assert_eq!(topic.route_level(), 255);
161 }
162
163 #[test]
164 fn routed_str_topic_as_route() {
165 let topic = create_routed_str_topic("test/route");
166 assert_eq!(topic.as_route(), "test/route");
167 }
168
169 #[test]
170 fn routed_str_topic_as_route_with_none_levels() {
171 let topic = create_routed_str_topic_with_levels::<255>("a/b/c/d/e");
172 assert_eq!(topic.as_route(), "a/b/c/d/e");
173 }
174
175 #[test]
176 fn routed_str_topic_as_route_with_zero_levels() {
177 let topic = create_routed_str_topic_with_levels::<0>("a/b/c/d/e");
178 assert_eq!(topic.as_route(), "");
179 }
180
181 #[test]
182 fn routed_str_topic_as_route_with_one_level() {
183 let topic = create_routed_str_topic_with_levels::<1>("a/b/c/d/e");
184 assert_eq!(topic.as_route(), "a");
185 }
186
187 #[test]
188 fn routed_str_topic_as_route_with_three_levels() {
189 let topic = create_routed_str_topic_with_levels::<3>("a/b/c/d/e");
190 assert_eq!(topic.as_route(), "a/b/c");
191 }
192
193 #[test]
194 fn routed_str_topic_as_route_with_more_levels_than_available() {
195 let topic = create_routed_str_topic_with_levels::<10>("a/b/c");
196 assert_eq!(topic.as_route(), "a/b/c");
197 }
198
199 #[test]
200 fn routed_str_topic_as_route_single_level_topic() {
201 let topic = create_routed_str_topic_with_levels::<1>("single");
202 assert_eq!(topic.as_route(), "single");
203 }
204
205 #[test]
206 fn routed_str_topic_as_route_single_level_topic_zero_levels() {
207 let topic = create_routed_str_topic_with_levels::<0>("single");
208 assert_eq!(topic.as_route(), "");
209 }
210
211 #[test]
212 fn routed_str_topic_with_route_levels_constructor() {
213 let topic = create_routed_str_topic_with_levels::<2>("a/b/c/d");
214 assert_eq!(topic.to_string(), "a/b/c/d");
215 assert_eq!(topic.as_route(), "a/b");
216 }
217
218 #[test]
219 fn routed_str_topic_not_replace_at_at_level_0() {
220 let mut topic = create_routed_str_topic("a/b/c/d");
221 let result = topic.replace_at(0, "x");
222 assert!(result.is_err());
223 assert_eq!(result.unwrap_err(), StrTopicError::LevelZero);
224 assert_eq!(topic.to_string(), "a/b/c/d");
225 }
226
227 #[test]
228 fn routed_str_topic_replace_at_at_first_level() {
229 let mut topic = create_routed_str_topic("a/b/c/d");
230 let result = topic.replace_at(1, "x");
231 assert!(result.is_ok());
232 assert_eq!(topic.to_string(), "x/b/c/d");
233 }
234
235 #[test]
236 fn routed_str_topic_replace_at_at_level_2() {
237 let mut topic = create_routed_str_topic("a/b/c/d");
238 let result = topic.replace_at(2, "x");
239 assert!(result.is_ok());
240 assert_eq!(topic.to_string(), "a/x/c/d");
241 }
242
243 #[test]
244 fn routed_str_topic_replace_at_at_level_3() {
245 let mut topic = create_routed_str_topic("a/b/c/d");
246 let result = topic.replace_at(3, "x");
247 assert!(result.is_ok());
248 assert_eq!(topic.to_string(), "a/b/x/d");
249 }
250
251 #[test]
252 fn routed_str_topic_replace_at_at_last_level() {
253 let mut topic = create_routed_str_topic("a/b/c/d");
254 let result = topic.replace_at(4, "x");
255 assert!(result.is_ok());
256 assert_eq!(topic.to_string(), "a/b/c/x");
257 }
258
259 #[test]
260 fn routed_str_topic_not_replace_at_at_level_too_high() {
261 let mut topic = create_routed_str_topic("a/b/c/d");
262 let result = topic.replace_at(5, "x");
263 assert!(result.is_err());
264 assert_eq!(result.unwrap_err(), StrTopicError::LevelTooHigh(5));
265 assert_eq!(topic.to_string(), "a/b/c/d");
266 }
267}