1use crate::protocol::v5::reason_codes::ReasonCode;
2use thiserror::Error;
3
4pub type Result<T> = std::result::Result<T, MqttError>;
5
6#[derive(Error, Debug, Clone)]
36pub enum MqttError {
37 #[error("IO error: {0}")]
38 Io(String),
39
40 #[error("Invalid topic name: {0}")]
41 InvalidTopicName(String),
42
43 #[error("Invalid topic filter: {0}")]
44 InvalidTopicFilter(String),
45
46 #[error("Invalid client ID: {0}")]
47 InvalidClientId(String),
48
49 #[error("Connection error: {0}")]
50 ConnectionError(String),
51
52 #[error("Connection refused: {0:?}")]
53 ConnectionRefused(ReasonCode),
54
55 #[error("Protocol error: {0}")]
56 ProtocolError(String),
57
58 #[error("Malformed packet: {0}")]
59 MalformedPacket(String),
60
61 #[error("Packet too large: size {size} exceeds maximum {max}")]
62 PacketTooLarge { size: usize, max: usize },
63
64 #[error("Authentication failed")]
65 AuthenticationFailed,
66
67 #[error("Not authorized")]
68 NotAuthorized,
69
70 #[error("Not connected")]
71 NotConnected,
72
73 #[error("Already connected")]
74 AlreadyConnected,
75
76 #[error("Timeout")]
77 Timeout,
78
79 #[error("Subscription failed: {0:?}")]
80 SubscriptionFailed(ReasonCode),
81
82 #[error("Unsubscription failed: {0:?}")]
83 UnsubscriptionFailed(ReasonCode),
84
85 #[error("Publish failed: {0:?}")]
86 PublishFailed(ReasonCode),
87
88 #[error("Packet identifier not found: {0}")]
89 PacketIdNotFound(u16),
90
91 #[error("Packet identifier already in use: {0}")]
92 PacketIdInUse(u16),
93
94 #[error("Invalid QoS: {0}")]
95 InvalidQoS(u8),
96
97 #[error("Invalid packet type: {0}")]
98 InvalidPacketType(u8),
99
100 #[error("Invalid reason code: {0}")]
101 InvalidReasonCode(u8),
102
103 #[error("Invalid property ID: {0}")]
104 InvalidPropertyId(u8),
105
106 #[error("Duplicate property ID: {0}")]
107 DuplicatePropertyId(u8),
108
109 #[error("Session expired")]
110 SessionExpired,
111
112 #[error("Keep alive timeout")]
113 KeepAliveTimeout,
114
115 #[error("Server shutting down")]
116 ServerShuttingDown,
117
118 #[error("Client closed connection")]
119 ClientClosed,
120
121 #[error("Connection closed by peer")]
122 ConnectionClosedByPeer,
123
124 #[error("Maximum connect time exceeded")]
125 MaxConnectTime,
126
127 #[error("Topic alias invalid: {0}")]
128 TopicAliasInvalid(u16),
129
130 #[error("Receive maximum exceeded")]
131 ReceiveMaximumExceeded,
132
133 #[error("Will message rejected")]
134 WillRejected,
135
136 #[error("Implementation specific error: {0}")]
137 ImplementationSpecific(String),
138
139 #[error("Unsupported protocol version")]
140 UnsupportedProtocolVersion,
141
142 #[error("Invalid state: {0}")]
143 InvalidState(String),
144
145 #[error("Client identifier not valid")]
146 ClientIdentifierNotValid,
147
148 #[error("Bad username or password")]
149 BadUsernameOrPassword,
150
151 #[error("Server unavailable")]
152 ServerUnavailable,
153
154 #[error("Server busy")]
155 ServerBusy,
156
157 #[error("Banned")]
158 Banned,
159
160 #[error("Bad authentication method")]
161 BadAuthenticationMethod,
162
163 #[error("Quota exceeded")]
164 QuotaExceeded,
165
166 #[error("Payload format invalid")]
167 PayloadFormatInvalid,
168
169 #[error("Retain not supported")]
170 RetainNotSupported,
171
172 #[error("QoS not supported")]
173 QoSNotSupported,
174
175 #[error("Use another server")]
176 UseAnotherServer,
177
178 #[error("Server moved")]
179 ServerMoved,
180
181 #[error("Shared subscriptions not supported")]
182 SharedSubscriptionsNotSupported,
183
184 #[error("Connection rate exceeded")]
185 ConnectionRateExceeded,
186
187 #[error("Subscription identifiers not supported")]
188 SubscriptionIdentifiersNotSupported,
189
190 #[error("Wildcard subscriptions not supported")]
191 WildcardSubscriptionsNotSupported,
192
193 #[error("Message too large for queue")]
194 MessageTooLarge,
195
196 #[error("Flow control exceeded")]
197 FlowControlExceeded,
198
199 #[error("Packet ID exhausted")]
200 PacketIdExhausted,
201
202 #[error("String too long: {0} bytes exceeds maximum of 65535")]
203 StringTooLong(usize),
204
205 #[error("Configuration error: {0}")]
206 Configuration(String),
207}
208
209impl MqttError {
210 pub fn is_normal_disconnect(&self) -> bool {
211 match self {
212 Self::ClientClosed | Self::ConnectionClosedByPeer => true,
213 Self::Io(msg)
214 if msg.contains("stream has been shut down")
215 || msg.contains("Connection reset") =>
216 {
217 true
218 }
219 _ => false,
220 }
221 }
222}
223
224impl From<std::io::Error> for MqttError {
225 fn from(err: std::io::Error) -> Self {
226 MqttError::Io(err.to_string())
227 }
228}
229
230impl From<String> for MqttError {
232 fn from(msg: String) -> Self {
233 MqttError::MalformedPacket(msg)
234 }
235}
236
237impl From<&str> for MqttError {
238 fn from(msg: &str) -> Self {
239 MqttError::MalformedPacket(msg.to_string())
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use std::io;
247
248 #[test]
249 fn test_error_display() {
250 let err = MqttError::InvalidTopicName("test/+/topic".to_string());
251 assert_eq!(err.to_string(), "Invalid topic name: test/+/topic");
252
253 let err = MqttError::PacketTooLarge {
254 size: 1000,
255 max: 500,
256 };
257 assert_eq!(
258 err.to_string(),
259 "Packet too large: size 1000 exceeds maximum 500"
260 );
261
262 let err = MqttError::ConnectionRefused(ReasonCode::BadUsernameOrPassword);
263 assert_eq!(err.to_string(), "Connection refused: BadUsernameOrPassword");
264 }
265
266 #[test]
267 fn test_error_from_io() {
268 let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "test");
269 let mqtt_err: MqttError = io_err.into();
270 match mqtt_err {
271 MqttError::Io(e) => assert!(e.contains("test")),
272 _ => panic!("Expected Io error"),
273 }
274 }
275
276 #[test]
277 fn test_result_type() {
278 #[allow(clippy::unnecessary_wraps)]
279 fn returns_result() -> Result<String> {
280 Ok("success".to_string())
281 }
282
283 fn returns_error() -> Result<String> {
284 Err(MqttError::NotConnected)
285 }
286
287 assert!(returns_result().is_ok());
288 assert!(returns_error().is_err());
289 }
290}