1use std::{error::Error as StdError, io, ops::Deref, rc::Rc};
3
4use serde_json::error::Error as JsonError;
5
6use crate::error::ErrorDiagnostic;
7use crate::http::error::{DecodeError, EncodeError, HttpError, PayloadError};
8use crate::util::{Either, clone_io_error};
9
10#[derive(thiserror::Error, Debug)]
12pub enum JsonPayloadError {
13 #[error("Content type error")]
15 ContentType,
16 #[error("Json deserialize error")]
18 Deserialize(#[source] Option<JsonError>),
19 #[error("Error occurred while reading payload")]
21 Payload(
22 #[from]
23 #[source]
24 ClientPayloadError,
25 ),
26}
27
28impl Clone for JsonPayloadError {
29 fn clone(&self) -> Self {
30 match self {
31 JsonPayloadError::ContentType => JsonPayloadError::ContentType,
32 JsonPayloadError::Deserialize(_) => JsonPayloadError::Deserialize(None),
33 JsonPayloadError::Payload(err) => JsonPayloadError::Payload(err.clone()),
34 }
35 }
36}
37
38impl From<JsonError> for JsonPayloadError {
39 fn from(err: JsonError) -> JsonPayloadError {
40 JsonPayloadError::Deserialize(Some(err))
41 }
42}
43
44impl From<PayloadError> for JsonPayloadError {
45 fn from(err: PayloadError) -> JsonPayloadError {
46 JsonPayloadError::Payload(ClientPayloadError(err))
47 }
48}
49
50impl ErrorDiagnostic for JsonPayloadError {
51 fn signature(&self) -> &'static str {
52 match self {
53 JsonPayloadError::ContentType => "ntex-client-JsonContentType",
54 JsonPayloadError::Deserialize(_) => "ntex-client-JsonDeserialize",
55 JsonPayloadError::Payload(_) => "ntex-client-JsonPayload",
56 }
57 }
58}
59
60#[derive(thiserror::Error, Clone, Debug)]
61#[error("{0}")]
62pub struct ClientPayloadError(
64 #[from]
65 #[source]
66 pub(crate) PayloadError,
67);
68
69impl Deref for ClientPayloadError {
70 type Target = PayloadError;
71
72 fn deref(&self) -> &Self::Target {
73 &self.0
74 }
75}
76
77impl ErrorDiagnostic for ClientPayloadError {
78 fn signature(&self) -> &'static str {
79 "ntex-client-Payload"
80 }
81}
82
83#[derive(thiserror::Error, Debug)]
85pub enum ConnectError {
86 #[error("SSL is not supported")]
88 SslIsNotSupported,
89
90 #[error("Failed resolving hostname: {0}")]
92 Resolver(
93 #[from]
94 #[source]
95 io::Error,
96 ),
97
98 #[error("No dns records found for the input")]
100 NoRecords,
101
102 #[error("Connector has been disconnected")]
104 Disconnected(#[source] Option<io::Error>),
105
106 #[error("Invalid connect input")]
108 InvalidInput,
109
110 #[error("Connector received `Connect` method with unresolved host")]
112 Unresolved,
113}
114
115impl ErrorDiagnostic for ConnectError {
116 fn signature(&self) -> &'static str {
117 match self {
118 ConnectError::SslIsNotSupported => "ntex-client-connect-SslIsNotSupported",
119 ConnectError::Resolver(..) => "ntex-client-connect-Resolver",
120 ConnectError::NoRecords => "ntex-client-connect-NoRecords",
121 ConnectError::Disconnected(_) => "ntex-client-connect-Disconnected",
122 ConnectError::InvalidInput => "ntex-client-connect-InvalidInput",
123 ConnectError::Unresolved => "ntex-client-connect-Unresolved",
124 }
125 }
126}
127
128impl Clone for ConnectError {
129 fn clone(&self) -> Self {
130 match self {
131 ConnectError::SslIsNotSupported => ConnectError::SslIsNotSupported,
132 ConnectError::Resolver(e) => ConnectError::Resolver(clone_io_error(e)),
133 ConnectError::NoRecords => ConnectError::NoRecords,
134 ConnectError::Disconnected(e) => {
135 if let Some(e) = e {
136 ConnectError::Disconnected(Some(clone_io_error(e)))
137 } else {
138 ConnectError::Disconnected(None)
139 }
140 }
141 ConnectError::InvalidInput => ConnectError::InvalidInput,
142 ConnectError::Unresolved => ConnectError::Unresolved,
143 }
144 }
145}
146
147impl From<crate::connect::ConnectError> for ConnectError {
148 fn from(err: crate::connect::ConnectError) -> ConnectError {
149 match err {
150 crate::connect::ConnectError::Resolver(e) => ConnectError::Resolver(e),
151 crate::connect::ConnectError::NoRecords => ConnectError::NoRecords,
152 crate::connect::ConnectError::InvalidInput => ConnectError::InvalidInput,
153 crate::connect::ConnectError::Unresolved => ConnectError::Unresolved,
154 crate::connect::ConnectError::Io(e) => ConnectError::Disconnected(Some(e)),
155 }
156 }
157}
158
159#[derive(Copy, Clone, Debug, thiserror::Error)]
160pub enum InvalidUrl {
162 #[error("Missing url scheme")]
164 MissingScheme,
165 #[error("Unknown url scheme")]
167 UnknownScheme,
168 #[error("Missing host name")]
170 MissingHost,
171 #[error("Url parse error: {0}")]
173 Parse(
174 #[from]
175 #[source]
176 urly::InvalidUrl,
177 ),
178}
179
180impl From<std::convert::Infallible> for InvalidUrl {
181 fn from(err: std::convert::Infallible) -> InvalidUrl {
182 match err {}
183 }
184}
185
186#[derive(Debug, thiserror::Error)]
188pub enum ClientError {
189 #[error("Invalid URL: {0}")]
191 Url(
192 #[from]
193 #[source]
194 InvalidUrl,
195 ),
196 #[error("Failed to connect to host: {0}")]
198 Connect(
199 #[from]
200 #[source]
201 ConnectError,
202 ),
203 #[error("Error sending request: {0}")]
205 Send(
206 #[from]
207 #[source]
208 io::Error,
209 ),
210 #[error("Error during request encoding: {0}")]
212 Request(
213 #[from]
214 #[source]
215 EncodeError,
216 ),
217 #[error("Error during response parsing: {0}")]
219 Response(
220 #[from]
221 #[source]
222 DecodeError,
223 ),
224 #[error("{0}")]
226 Http(
227 #[from]
228 #[source]
229 HttpError,
230 ),
231 #[error("Http2 error {0}")]
233 H2(
234 #[from]
235 #[source]
236 ntex_h2::OperationError,
237 ),
238 #[error("Timeout while waiting for response")]
240 Timeout,
241 #[error("{0}")]
243 Error(
244 #[from]
245 #[source]
246 Rc<dyn StdError>,
247 ),
248}
249
250impl Clone for ClientError {
251 fn clone(&self) -> ClientError {
252 match self {
253 ClientError::Url(err) => ClientError::Url(*err),
254 ClientError::Connect(err) => ClientError::Connect(err.clone()),
255 ClientError::Request(err) => ClientError::Request(err.clone()),
256 ClientError::Response(err) => ClientError::Response(*err),
257 ClientError::Http(err) => ClientError::Http(*err),
258 ClientError::H2(err) => ClientError::H2(*err),
259 ClientError::Timeout => ClientError::Timeout,
260 ClientError::Error(err) => ClientError::Error(err.clone()),
261 ClientError::Send(err) => ClientError::Send(crate::util::clone_io_error(err)),
262 }
263 }
264}
265
266impl From<Either<EncodeError, io::Error>> for ClientError {
267 fn from(err: Either<EncodeError, io::Error>) -> Self {
268 match err {
269 Either::Left(err) => ClientError::Request(err),
270 Either::Right(err) => ClientError::Send(err),
271 }
272 }
273}
274
275impl From<Either<DecodeError, io::Error>> for ClientError {
276 fn from(err: Either<DecodeError, io::Error>) -> Self {
277 match err {
278 Either::Left(err) => ClientError::Response(err),
279 Either::Right(err) => ClientError::Send(err),
280 }
281 }
282}
283
284impl ErrorDiagnostic for ClientError {
285 fn signature(&self) -> &'static str {
286 match self {
287 ClientError::Url(_) => "ntex-client-Url",
288 ClientError::Http(_) => "ntex-client-Http",
289 ClientError::Connect(err) => err.signature(),
290 ClientError::Send(err) => err.signature(),
291 ClientError::Request(_) => "ntex-client-Request",
292 ClientError::Response(_) => "ntex-client-Response",
293 ClientError::Timeout => "ntex-client-Timeout",
294 ClientError::Error(_) => "ntex-client-Error",
295 ClientError::H2(err) => err.signature(),
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn connect_error_from() {
306 let err = ConnectError::from(crate::connect::ConnectError::InvalidInput);
307 assert!(matches!(err, ConnectError::InvalidInput));
308 assert_eq!(err.signature(), "ntex-client-connect-InvalidInput");
309 assert!(matches!(err.clone(), ConnectError::InvalidInput));
310 }
311
312 #[test]
313 fn connect_error_conversions() {
314 use crate::connect::ConnectError as Base;
315
316 let cases = [
317 (
318 Base::Resolver(io::Error::other("dns")),
319 "ntex-client-connect-Resolver",
320 ),
321 (Base::NoRecords, "ntex-client-connect-NoRecords"),
322 (Base::Unresolved, "ntex-client-connect-Unresolved"),
323 (
324 Base::Io(io::Error::other("io")),
325 "ntex-client-connect-Disconnected",
326 ),
327 ];
328 for (err, sig) in cases {
329 let err = ConnectError::from(err);
330 assert_eq!(err.signature(), sig);
331 assert_eq!(err.clone().signature(), sig);
332 }
333 for err in [
334 ConnectError::SslIsNotSupported,
335 ConnectError::Disconnected(None),
336 ] {
337 assert_eq!(err.clone().to_string(), err.to_string());
338 assert_eq!(err.clone().signature(), err.signature());
339 }
340
341 let err = ConnectError::Disconnected(Some(io::Error::other("gone")));
342 let ConnectError::Disconnected(Some(e)) = err.clone() else {
343 panic!()
344 };
345 assert_eq!(e.kind(), io::ErrorKind::Other);
346 assert!(e.to_string().contains("gone"), "{e}");
347 }
348
349 #[test]
350 fn client_error_clone_and_signature() {
351 let errs = [
352 (
353 ClientError::from(InvalidUrl::MissingHost),
354 "ntex-client-Url",
355 ),
356 (
357 ClientError::from(ConnectError::NoRecords),
358 "ntex-client-connect-NoRecords",
359 ),
360 (
361 ClientError::from(Either::<EncodeError, io::Error>::Left(
362 EncodeError::UnexpectedEof,
363 )),
364 "ntex-client-Request",
365 ),
366 (
367 ClientError::from(Either::<DecodeError, io::Error>::Left(DecodeError::Header)),
368 "ntex-client-Response",
369 ),
370 (
371 ClientError::from(HttpError::from(
372 crate::http::header::HeaderName::try_from("\n").unwrap_err(),
373 )),
374 "ntex-client-Http",
375 ),
376 (ClientError::Timeout, "ntex-client-Timeout"),
377 (
378 ClientError::from(Rc::new(io::Error::other("other")) as Rc<dyn StdError>),
379 "ntex-client-Error",
380 ),
381 (
382 ClientError::from(ntex_h2::OperationError::Disconnected),
383 ntex_h2::OperationError::Disconnected.signature(),
384 ),
385 ];
386 for (err, sig) in errs {
387 assert_eq!(err.signature(), sig, "{err:?}");
388 let cloned = err.clone();
389 assert_eq!(cloned.signature(), sig);
390 if !matches!(err, ClientError::Connect(_)) {
391 assert_eq!(cloned.to_string(), err.to_string());
392 }
393 }
394
395 let io_err = |msg| io::Error::new(io::ErrorKind::BrokenPipe, msg);
396 for (err, msg) in [
397 (
398 ClientError::from(Either::<EncodeError, _>::Right(io_err("write"))),
399 "write",
400 ),
401 (
402 ClientError::from(Either::<DecodeError, _>::Right(io_err("read"))),
403 "read",
404 ),
405 ] {
406 let ClientError::Send(e) = err.clone() else {
407 panic!("{err:?}")
408 };
409 assert_eq!(e.kind(), io::ErrorKind::BrokenPipe);
411 assert!(e.to_string().contains(msg), "{e}");
412 assert_eq!(err.signature(), e.signature());
413 }
414 }
415
416 #[test]
417 fn payload_errors() {
418 let err = ClientPayloadError::from(PayloadError::Overflow);
419 assert_eq!(err.signature(), "ntex-client-Payload");
420 assert!(matches!(*err, PayloadError::Overflow));
421
422 let json_err = serde_json::from_str::<u32>("x").unwrap_err();
423 let errs = [
424 (JsonPayloadError::ContentType, "ntex-client-JsonContentType"),
425 (
426 JsonPayloadError::from(json_err),
427 "ntex-client-JsonDeserialize",
428 ),
429 (
430 JsonPayloadError::from(PayloadError::Overflow),
431 "ntex-client-JsonPayload",
432 ),
433 ];
434 for (err, sig) in errs {
435 assert_eq!(err.signature(), sig);
436 assert_eq!(err.clone().signature(), sig);
437 }
438 let err = JsonPayloadError::from(serde_json::from_str::<u32>("x").unwrap_err());
440 assert!(matches!(err.clone(), JsonPayloadError::Deserialize(None)));
441 assert!(matches!(
442 JsonPayloadError::from(PayloadError::Overflow).clone(),
443 JsonPayloadError::Payload(ClientPayloadError(PayloadError::Overflow))
444 ));
445 }
446}