1use std::{
4 borrow::{Borrow, BorrowMut},
5 io::{self, Read, Write},
6 ops::{Deref, DerefMut},
7};
8
9use crate::{Decodable, Encodable};
10
11#[inline]
12fn is_invalid_topic_name(topic_name: &str) -> bool {
13 topic_name.is_empty() || topic_name.as_bytes().len() > 65535 || topic_name.chars().any(|ch| ch == '#' || ch == '+')
14}
15
16#[derive(Debug, Eq, PartialEq, Clone, Hash, Ord, PartialOrd)]
20pub struct TopicName(String);
21
22impl TopicName {
23 pub fn new<S: Into<String>>(topic_name: S) -> Result<TopicName, TopicNameError> {
26 let topic_name = topic_name.into();
27 if is_invalid_topic_name(&topic_name) {
28 Err(TopicNameError(topic_name))
29 } else {
30 Ok(TopicName(topic_name))
31 }
32 }
33
34 pub unsafe fn new_unchecked(topic_name: String) -> TopicName {
41 TopicName(topic_name)
42 }
43}
44
45impl From<TopicName> for String {
46 fn from(topic_name: TopicName) -> String {
47 topic_name.0
48 }
49}
50
51impl Deref for TopicName {
52 type Target = TopicNameRef;
53
54 fn deref(&self) -> &TopicNameRef {
55 unsafe { TopicNameRef::new_unchecked(&self.0) }
56 }
57}
58
59impl DerefMut for TopicName {
60 fn deref_mut(&mut self) -> &mut Self::Target {
61 unsafe { TopicNameRef::new_mut_unchecked(&mut self.0) }
62 }
63}
64
65impl Borrow<TopicNameRef> for TopicName {
66 fn borrow(&self) -> &TopicNameRef {
67 Deref::deref(self)
68 }
69}
70
71impl BorrowMut<TopicNameRef> for TopicName {
72 fn borrow_mut(&mut self) -> &mut TopicNameRef {
73 DerefMut::deref_mut(self)
74 }
75}
76
77impl Encodable for TopicName {
78 fn encode<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
79 (&self.0[..]).encode(writer)
80 }
81
82 fn encoded_length(&self) -> u32 {
83 (&self.0[..]).encoded_length()
84 }
85}
86
87impl Decodable for TopicName {
88 type Error = TopicNameDecodeError;
89 type Cond = ();
90
91 fn decode_with<R: Read>(reader: &mut R, _rest: ()) -> Result<TopicName, TopicNameDecodeError> {
92 let topic_name = String::decode(reader)?;
93 Ok(TopicName::new(topic_name)?)
94 }
95}
96
97#[derive(Debug, thiserror::Error)]
98#[error("invalid topic filter ({0})")]
99pub struct TopicNameError(pub String);
100
101#[derive(Debug, thiserror::Error)]
103#[error(transparent)]
104pub enum TopicNameDecodeError {
105 IoError(#[from] io::Error),
106 InvalidTopicName(#[from] TopicNameError),
107}
108
109#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
111#[repr(transparent)]
112pub struct TopicNameRef(str);
113
114impl TopicNameRef {
115 pub fn new<S: AsRef<str> + ?Sized>(topic_name: &S) -> Result<&TopicNameRef, TopicNameError> {
118 let topic_name = topic_name.as_ref();
119 if is_invalid_topic_name(topic_name) {
120 Err(TopicNameError(topic_name.to_owned()))
121 } else {
122 Ok(unsafe { &*(topic_name as *const str as *const TopicNameRef) })
123 }
124 }
125
126 pub fn new_mut<S: AsMut<str> + ?Sized>(topic_name: &mut S) -> Result<&mut TopicNameRef, TopicNameError> {
129 let topic_name = topic_name.as_mut();
130 if is_invalid_topic_name(topic_name) {
131 Err(TopicNameError(topic_name.to_owned()))
132 } else {
133 Ok(unsafe { &mut *(topic_name as *mut str as *mut TopicNameRef) })
134 }
135 }
136
137 pub unsafe fn new_unchecked<S: AsRef<str> + ?Sized>(topic_name: &S) -> &TopicNameRef {
144 let topic_name = topic_name.as_ref();
145 &*(topic_name as *const str as *const TopicNameRef)
146 }
147
148 pub unsafe fn new_mut_unchecked<S: AsMut<str> + ?Sized>(topic_name: &mut S) -> &mut TopicNameRef {
155 let topic_name = topic_name.as_mut();
156 &mut *(topic_name as *mut str as *mut TopicNameRef)
157 }
158
159 pub fn is_server_specific(&self) -> bool {
163 self.0.starts_with('$')
164 }
165}
166
167impl Deref for TopicNameRef {
168 type Target = str;
169
170 fn deref(&self) -> &str {
171 &self.0
172 }
173}
174
175impl ToOwned for TopicNameRef {
176 type Owned = TopicName;
177
178 fn to_owned(&self) -> Self::Owned {
179 TopicName(self.0.to_owned())
180 }
181}
182
183impl Encodable for TopicNameRef {
184 fn encode<W: Write>(&self, writer: &mut W) -> Result<(), io::Error> {
185 (&self.0[..]).encode(writer)
186 }
187
188 fn encoded_length(&self) -> u32 {
189 (&self.0[..]).encoded_length()
190 }
191}
192
193#[cfg(test)]
194mod test {
195 use super::*;
196
197 #[test]
198 fn topic_name_sys() {
199 let topic_name = "$SYS".to_owned();
200 TopicName::new(topic_name).unwrap();
201
202 let topic_name = "$SYS/broker/connection/test.cosm-energy/state".to_owned();
203 TopicName::new(topic_name).unwrap();
204 }
205
206 #[test]
207 fn topic_name_slash() {
208 TopicName::new("/").unwrap();
209 }
210
211 #[test]
212 fn topic_name_basic() {
213 TopicName::new("/finance").unwrap();
214 TopicName::new("/finance//def").unwrap();
215 }
216}