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