1use std::io;
3
4use crate::error::ErrorDiagnostic;
5use crate::http::error::{DecodeError, EncodeError, ResponseError};
6use crate::http::header::{ALLOW, HeaderValue, SEC_WEBSOCKET_VERSION};
7use crate::http::{Response, StatusCode};
8use crate::{connect::ConnectError, util::Either, util::clone_io_error};
9
10use super::OpCode;
11
12#[derive(Debug, thiserror::Error)]
14pub enum WsError<E> {
15 #[error("Service error")]
17 Service(#[source] E),
18 #[error("Keep-alive error")]
20 KeepAlive,
21 #[error("Frame read timeout")]
23 ReadTimeout,
24 #[error("Write timeout")]
26 WriteTimeout,
27 #[error("Ws protocol level error")]
29 Protocol(#[source] ProtocolError),
30 #[error("Ws handshake error")]
32 Handshake(#[from] HandshakeError),
33 #[error("Peer has been disconnected: {0:?}")]
35 Disconnected(#[source] Option<io::Error>),
36}
37
38#[derive(Copy, Clone, Debug, thiserror::Error)]
40pub enum ProtocolError {
41 #[error("Received an unmasked frame from client")]
43 UnmaskedFrame,
44 #[error("Received a masked frame from server")]
46 MaskedFrame,
47 #[error("Invalid opcode: {0}")]
49 InvalidOpcode(u8),
50 #[error("Reserved frame bits are set: {0:#05b}")]
52 ReservedBits(u8),
53 #[error("Fragmented control frame: {0}")]
55 FragmentedControlFrame(OpCode),
56 #[error("Invalid control frame length: {0}")]
58 InvalidLength(usize),
59 #[error("Invalid payload length encoding")]
61 InvalidLengthEncoding,
62 #[error("Invalid close status code: {0}")]
64 InvalidCloseCode(u16),
65 #[error("Invalid close-frame payload")]
67 InvalidClosePayload,
68 #[error("Invalid UTF-8 in close-frame description")]
70 InvalidUtf8,
71 #[error("WebSocket codec is closed")]
73 Closed,
74 #[error("A payload reached size limit.")]
76 Overflow,
77 #[error("Continuation is not started.")]
79 ContinuationNotStarted,
80 #[error("Received new continuation but it is already started")]
82 ContinuationStarted,
83}
84
85#[derive(Clone, Debug, thiserror::Error)]
87pub enum WsConfigError {
88 #[error("Missing url scheme")]
90 MissingScheme,
91 #[error("Unknown url scheme")]
93 UnknownScheme,
94 #[error("Missing host name")]
96 MissingHost,
97 #[error("Url parse error: {0}")]
99 Parse(
100 #[from]
101 #[source]
102 urly::InvalidUrl,
103 ),
104}
105
106impl From<std::convert::Infallible> for WsConfigError {
107 fn from(err: std::convert::Infallible) -> WsConfigError {
108 match err {}
109 }
110}
111
112#[derive(Debug, thiserror::Error)]
114pub enum WsClientError {
115 #[error("Invalid client configuration: {0}")]
117 Config(
118 #[from]
119 #[source]
120 WsConfigError,
121 ),
122 #[error("Invalid request")]
124 InvalidRequest(
125 #[from]
126 #[source]
127 EncodeError,
128 ),
129 #[error("Invalid response")]
131 InvalidResponse(
132 #[from]
133 #[source]
134 DecodeError,
135 ),
136 #[error("Invalid response status: {0}")]
138 InvalidResponseStatus(StatusCode),
139 #[error("Invalid upgrade header")]
141 InvalidUpgradeHeader,
142 #[error("Invalid connection header")]
144 InvalidConnectionHeader(HeaderValue),
145 #[error("Missing CONNECTION header")]
147 MissingConnectionHeader,
148 #[error("Missing SEC-WEBSOCKET-ACCEPT header")]
150 MissingWebSocketAcceptHeader,
151 #[error("Invalid challenge response")]
153 InvalidChallengeResponse(String, HeaderValue),
154 #[error("Invalid WebSocket subprotocol: {0:?}")]
156 InvalidWebSocketProtocol(HeaderValue),
157 #[error("Unexpected WebSocket extensions: {0:?}")]
159 UnexpectedWebSocketExtensions(HeaderValue),
160 #[error("{0}")]
162 Protocol(
163 #[from]
164 #[source]
165 ProtocolError,
166 ),
167 #[error("Timeout while waiting for response")]
169 Timeout,
170 #[error("Failed to connect to host: {0}")]
172 Connect(
173 #[from]
174 #[source]
175 ConnectError,
176 ),
177 #[error("Connector has been disconnected: {0:?}")]
179 Disconnected(#[source] Option<io::Error>),
180}
181
182impl From<Either<DecodeError, io::Error>> for WsClientError {
183 fn from(err: Either<DecodeError, io::Error>) -> Self {
184 match err {
185 Either::Left(err) => WsClientError::InvalidResponse(err),
186 Either::Right(err) => WsClientError::Disconnected(Some(err)),
187 }
188 }
189}
190
191impl From<Either<EncodeError, io::Error>> for WsClientError {
192 fn from(err: Either<EncodeError, io::Error>) -> Self {
193 match err {
194 Either::Left(err) => WsClientError::InvalidRequest(err),
195 Either::Right(err) => WsClientError::Disconnected(Some(err)),
196 }
197 }
198}
199
200impl Clone for WsClientError {
201 fn clone(&self) -> Self {
202 match self {
203 WsClientError::Config(err) => WsClientError::Config(err.clone()),
204 WsClientError::InvalidRequest(err) => WsClientError::InvalidRequest(err.clone()),
205 WsClientError::InvalidResponse(err) => WsClientError::InvalidResponse(*err),
206 WsClientError::InvalidResponseStatus(err) => WsClientError::InvalidResponseStatus(*err),
207 WsClientError::InvalidUpgradeHeader => WsClientError::InvalidUpgradeHeader,
208 WsClientError::InvalidConnectionHeader(err) => {
209 WsClientError::InvalidConnectionHeader(err.clone())
210 }
211 WsClientError::MissingConnectionHeader => WsClientError::MissingConnectionHeader,
212 WsClientError::MissingWebSocketAcceptHeader => {
213 WsClientError::MissingWebSocketAcceptHeader
214 }
215 WsClientError::InvalidChallengeResponse(n, val) => {
216 WsClientError::InvalidChallengeResponse(n.clone(), val.clone())
217 }
218 WsClientError::InvalidWebSocketProtocol(val) => {
219 WsClientError::InvalidWebSocketProtocol(val.clone())
220 }
221 WsClientError::UnexpectedWebSocketExtensions(val) => {
222 WsClientError::UnexpectedWebSocketExtensions(val.clone())
223 }
224 WsClientError::Protocol(err) => WsClientError::Protocol(*err),
225 WsClientError::Timeout => WsClientError::Timeout,
226 WsClientError::Connect(err) => WsClientError::Connect(err.clone()),
227 WsClientError::Disconnected(err) => {
228 WsClientError::Disconnected(err.as_ref().map(clone_io_error))
229 }
230 }
231 }
232}
233
234impl ErrorDiagnostic for WsClientError {
235 fn signature(&self) -> &'static str {
236 "ntex-ws-client"
237 }
238}
239
240#[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)]
242pub enum HandshakeError {
243 #[error("Method not allowed")]
245 GetMethodRequired,
246 #[error("Websocket upgrade is expected")]
248 NoWebsocketUpgrade,
249 #[error("Connection upgrade is expected")]
251 NoConnectionUpgrade,
252 #[error("Websocket version header is required")]
254 NoVersionHeader,
255 #[error("Unsupported version")]
257 UnsupportedVersion,
258 #[error("Unknown websocket key")]
260 BadWebsocketKey,
261 #[error("Invalid websocket subprotocol")]
263 BadWebsocketProtocol,
264}
265
266impl ResponseError for HandshakeError {
267 fn error_response(&self) -> Response {
268 match *self {
269 HandshakeError::GetMethodRequired => {
270 Response::MethodNotAllowed().header(ALLOW, "GET").build()
271 }
272 HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
273 .reason("No WebSocket UPGRADE header found")
274 .build(),
275 HandshakeError::NoConnectionUpgrade => Response::BadRequest()
276 .reason("No CONNECTION upgrade")
277 .build(),
278 HandshakeError::NoVersionHeader => Response::BadRequest()
279 .reason("Websocket version header is required")
280 .header(SEC_WEBSOCKET_VERSION, "13")
281 .build(),
282 HandshakeError::UnsupportedVersion => Response::BadRequest()
283 .reason("Unsupported version")
284 .header(SEC_WEBSOCKET_VERSION, "13")
285 .build(),
286 HandshakeError::BadWebsocketKey => {
287 Response::BadRequest().reason("Handshake error").build()
288 }
289 HandshakeError::BadWebsocketProtocol => Response::BadRequest()
290 .reason("Invalid websocket subprotocol")
291 .build(),
292 }
293 }
294}
295
296impl ResponseError for ProtocolError {}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_client_error_from_either() {
304 let err = WsClientError::from(Either::<DecodeError, io::Error>::Left(DecodeError::Method));
305 assert!(matches!(
306 err,
307 WsClientError::InvalidResponse(DecodeError::Method)
308 ));
309 let err = WsClientError::from(Either::<DecodeError, _>::Right(io::Error::other("x")));
310 assert!(matches!(err, WsClientError::Disconnected(Some(_))));
311
312 let err = WsClientError::from(Either::<EncodeError, io::Error>::Left(
313 EncodeError::UnexpectedEof,
314 ));
315 assert!(matches!(
316 err,
317 WsClientError::InvalidRequest(EncodeError::UnexpectedEof)
318 ));
319 let err = WsClientError::from(Either::<EncodeError, _>::Right(io::Error::other("x")));
320 assert!(matches!(err, WsClientError::Disconnected(Some(_))));
321 }
322
323 #[test]
324 fn test_client_error_clone() {
325 let hdr = HeaderValue::from_static("v");
326 let errs = [
327 WsClientError::Config(WsConfigError::MissingHost),
328 WsClientError::InvalidRequest(EncodeError::UnexpectedEof),
329 WsClientError::InvalidResponse(DecodeError::Method),
330 WsClientError::InvalidResponseStatus(StatusCode::OK),
331 WsClientError::InvalidUpgradeHeader,
332 WsClientError::InvalidConnectionHeader(hdr.clone()),
333 WsClientError::MissingConnectionHeader,
334 WsClientError::MissingWebSocketAcceptHeader,
335 WsClientError::InvalidChallengeResponse("key".into(), hdr.clone()),
336 WsClientError::InvalidWebSocketProtocol(hdr.clone()),
337 WsClientError::UnexpectedWebSocketExtensions(hdr),
338 WsClientError::Protocol(ProtocolError::Overflow),
339 WsClientError::Timeout,
340 WsClientError::Connect(ConnectError::Unresolved),
341 WsClientError::Disconnected(None),
342 WsClientError::Disconnected(Some(io::Error::other("disconnected"))),
343 ];
344 for err in errs {
345 assert_eq!(err.clone().to_string(), err.to_string());
346 assert_eq!(format!("{:?}", err.clone()), format!("{err:?}"));
347 assert_eq!(err.signature(), "ntex-ws-client");
348 }
349 }
350}