mqtt/
topic_name.rs

1//! Topic name
2
3use 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/// Topic name
17///
18/// <http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106>
19#[derive(Debug, Eq, PartialEq, Clone, Hash, Ord, PartialOrd)]
20pub struct TopicName(String);
21
22impl TopicName {
23    /// Creates a new topic name from string
24    /// Return error if the string is not a valid topic name
25    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    /// Creates a new topic name from string without validation
35    ///
36    /// # Safety
37    ///
38    /// Topic names' syntax is defined in [MQTT specification](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106).
39    /// Creating a name from raw string may cause errors
40    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/// Errors while parsing topic names
102#[derive(Debug, thiserror::Error)]
103#[error(transparent)]
104pub enum TopicNameDecodeError {
105    IoError(#[from] io::Error),
106    InvalidTopicName(#[from] TopicNameError),
107}
108
109/// Reference to a topic name
110#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
111#[repr(transparent)]
112pub struct TopicNameRef(str);
113
114impl TopicNameRef {
115    /// Creates a new topic name from string
116    /// Return error if the string is not a valid topic name
117    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    /// Creates a new topic name from string
127    /// Return error if the string is not a valid topic name
128    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    /// Creates a new topic name from string without validation
138    ///
139    /// # Safety
140    ///
141    /// Topic names' syntax is defined in [MQTT specification](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106).
142    /// Creating a name from raw string may cause errors
143    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    /// Creates a new topic name from string without validation
149    ///
150    /// # Safety
151    ///
152    /// Topic names' syntax is defined in [MQTT specification](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106).
153    /// Creating a name from raw string may cause errors
154    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    /// Check if this topic name is only for server.
160    ///
161    /// Topic names that beginning with a '$' character are reserved for servers
162    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}