1// Copyright (c) 2023 Huawei Device Co., Ltd.
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! Definition of `HttpClientErrors` which includes errors that may occur in
15//! this crate.
16
17use core::fmt::{Debug, Display, Formatter};
18use std::sync::Once;
19use std::{error, io};
20
21/// The structure encapsulates errors that can be encountered when working with
22/// the HTTP client.
23///
24/// # Examples
25///
26/// ```
27/// use ylong_http_client::HttpClientError;
28///
29/// let error = HttpClientError::user_aborted();
30/// ```
31pub struct HttpClientError {
32    kind: ErrorKind,
33    cause: Cause,
34}
35
36impl HttpClientError {
37    /// Creates a `UserAborted` error.
38    ///
39    /// # Examples
40    ///
41    /// ```
42    /// use ylong_http_client::HttpClientError;
43    ///
44    /// let user_aborted = HttpClientError::user_aborted();
45    /// ```
46    pub fn user_aborted() -> Self {
47        Self {
48            kind: ErrorKind::UserAborted,
49            cause: Cause::NoReason,
50        }
51    }
52
53    /// Creates an `Other` error.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use ylong_http_client::HttpClientError;
59    ///
60    /// fn error(error: std::io::Error) {
61    ///     let other = HttpClientError::other(error);
62    /// }
63    /// ```
64    pub fn other<T>(cause: T) -> Self
65    where
66        T: Into<Box<dyn error::Error + Send + Sync>>,
67    {
68        Self {
69            kind: ErrorKind::Other,
70            cause: Cause::Other(cause.into()),
71        }
72    }
73
74    /// Gets the `ErrorKind` of this `HttpClientError`.
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// use ylong_http_client::{ErrorKind, HttpClientError};
80    ///
81    /// let user_aborted = HttpClientError::user_aborted();
82    /// assert_eq!(user_aborted.error_kind(), ErrorKind::UserAborted);
83    /// ```
84    pub fn error_kind(&self) -> ErrorKind {
85        self.kind
86    }
87
88    /// Gets the `io::Error` if this `HttpClientError` comes from an
89    /// `io::Error`.
90    ///
91    /// Returns `None` if the `HttpClientError` doesn't come from an
92    /// `io::Error`.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use ylong_http_client::HttpClientError;
98    ///
99    /// let error = HttpClientError::user_aborted().io_error();
100    /// ```
101    pub fn io_error(&self) -> Option<&io::Error> {
102        match self.cause {
103            Cause::Io(ref io) => Some(io),
104            _ => None,
105        }
106    }
107
108    /// Check whether the cause of the error is dns error
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use ylong_http_client::HttpClientError;
114    ///
115    /// assert!(!HttpClientError::user_aborted().is_dns_error())
116    /// ```
117    pub fn is_dns_error(&self) -> bool {
118        matches!(self.cause, Cause::Dns(_))
119    }
120
121    /// Check whether the cause of the error is tls connection error
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use ylong_http_client::HttpClientError;
127    ///
128    /// assert!(!HttpClientError::user_aborted().is_tls_error())
129    /// ```
130    #[cfg(feature = "__tls")]
131    pub fn is_tls_error(&self) -> bool {
132        matches!(self.cause, Cause::Tls(_))
133    }
134}
135
136impl HttpClientError {
137    pub(crate) fn from_error<T>(kind: ErrorKind, err: T) -> Self
138    where
139        T: Into<Box<dyn error::Error + Send + Sync>>,
140    {
141        Self {
142            kind,
143            cause: Cause::Other(err.into()),
144        }
145    }
146
147    #[cfg(feature = "__tls")]
148    pub(crate) fn from_tls_error<T>(kind: ErrorKind, err: T) -> Self
149    where
150        T: Into<Box<dyn error::Error + Send + Sync>>,
151    {
152        Self {
153            kind,
154            cause: Cause::Tls(err.into()),
155        }
156    }
157
158    pub(crate) fn from_str(kind: ErrorKind, msg: &'static str) -> Self {
159        Self {
160            kind,
161            cause: Cause::Msg(msg),
162        }
163    }
164
165    pub(crate) fn from_io_error(kind: ErrorKind, err: io::Error) -> Self {
166        Self {
167            kind,
168            cause: Cause::Io(err),
169        }
170    }
171
172    pub(crate) fn from_dns_error(kind: ErrorKind, err: io::Error) -> Self {
173        Self {
174            kind,
175            cause: Cause::Dns(err),
176        }
177    }
178}
179
180impl Debug for HttpClientError {
181    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
182        let mut builder = f.debug_struct("HttpClientError");
183        builder.field("ErrorKind", &self.kind);
184        builder.field("Cause", &self.cause);
185        builder.finish()
186    }
187}
188
189impl Display for HttpClientError {
190    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
191        f.write_str(self.kind.as_str())?;
192        write!(f, ": {}", self.cause)?;
193        Ok(())
194    }
195}
196
197impl error::Error for HttpClientError {
198    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
199        static mut USER_ABORTED: Option<Box<dyn error::Error>> = None;
200        static ONCE: Once = Once::new();
201
202        ONCE.call_once(|| {
203            unsafe { USER_ABORTED = Some(Box::new(HttpClientError::user_aborted())) };
204        });
205
206        if self.kind == ErrorKind::UserAborted {
207            return unsafe { USER_ABORTED.as_ref().map(|e| e.as_ref()) };
208        }
209        None
210    }
211}
212
213/// Error kinds which can indicate the type of `HttpClientError`.
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub enum ErrorKind {
216    /// Errors for decoding response body.
217    BodyDecode,
218
219    /// Errors for transferring request body or response body.
220    BodyTransfer,
221
222    /// Errors for using various builder.
223    Build,
224
225    /// Errors for connecting to a server.
226    Connect,
227
228    /// Errors for upgrading a connection.
229    ConnectionUpgrade,
230
231    /// Other error kinds.
232    Other,
233
234    /// Errors for following redirect.
235    Redirect,
236
237    /// Errors for sending a request.
238    Request,
239
240    /// Errors for reaching a timeout.
241    Timeout,
242
243    /// User raised errors.
244    UserAborted,
245}
246
247impl ErrorKind {
248    /// Gets the string info of this `ErrorKind`.
249    ///
250    /// # Examples
251    ///
252    /// ```
253    /// # use ylong_http_client::ErrorKind;
254    ///
255    /// assert_eq!(ErrorKind::UserAborted.as_str(), "User Aborted Error");
256    /// ```
257    pub fn as_str(&self) -> &'static str {
258        match self {
259            Self::BodyDecode => "Body Decode Error",
260            Self::BodyTransfer => "Body Transfer Error",
261            Self::Build => "Build Error",
262            Self::Connect => "Connect Error",
263            Self::ConnectionUpgrade => "Connection Upgrade Error",
264            Self::Other => "Other Error",
265            Self::Redirect => "Redirect Error",
266            Self::Request => "Request Error",
267            Self::Timeout => "Timeout Error",
268            Self::UserAborted => "User Aborted Error",
269        }
270    }
271}
272
273pub(crate) enum Cause {
274    NoReason,
275    Dns(io::Error),
276    #[cfg(feature = "__tls")]
277    Tls(Box<dyn error::Error + Send + Sync>),
278    Io(io::Error),
279    Msg(&'static str),
280    Other(Box<dyn error::Error + Send + Sync>),
281}
282
283impl Debug for Cause {
284    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
285        match self {
286            Self::NoReason => write!(f, "No reason"),
287            Self::Dns(err) => Debug::fmt(err, f),
288            #[cfg(feature = "__tls")]
289            Self::Tls(err) => Debug::fmt(err, f),
290            Self::Io(err) => Debug::fmt(err, f),
291            Self::Msg(msg) => write!(f, "{}", msg),
292            Self::Other(err) => Debug::fmt(err, f),
293        }
294    }
295}
296
297impl Display for Cause {
298    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
299        match self {
300            Self::NoReason => write!(f, "No reason"),
301            Self::Dns(err) => Display::fmt(err, f),
302            #[cfg(feature = "__tls")]
303            Self::Tls(err) => Display::fmt(err, f),
304            Self::Io(err) => Display::fmt(err, f),
305            Self::Msg(msg) => write!(f, "{}", msg),
306            Self::Other(err) => Display::fmt(err, f),
307        }
308    }
309}
310
311macro_rules! err_from_other {
312    ($kind: ident, $err: expr) => {{
313        use crate::error::{ErrorKind, HttpClientError};
314
315        Err(HttpClientError::from_error(ErrorKind::$kind, $err))
316    }};
317}
318
319macro_rules! err_from_io {
320    ($kind: ident, $err: expr) => {{
321        use crate::error::{ErrorKind, HttpClientError};
322
323        Err(HttpClientError::from_io_error(ErrorKind::$kind, $err))
324    }};
325}
326
327macro_rules! err_from_msg {
328    ($kind: ident, $msg: literal) => {{
329        use crate::error::{ErrorKind, HttpClientError};
330
331        Err(HttpClientError::from_str(ErrorKind::$kind, $msg))
332    }};
333}
334
335#[cfg(test)]
336mod ut_util_error {
337    use std::io;
338
339    use crate::{ErrorKind, HttpClientError};
340
341    /// UT test cases for `ErrorKind::as_str`.
342    ///
343    /// # Brief
344    /// 1. Transfer ErrorKind to str a by calling `ErrorKind::as_str`.
345    /// 2. Checks if the results are correct.
346    #[test]
347    fn ut_err_as_str() {
348        assert_eq!(ErrorKind::BodyDecode.as_str(), "Body Decode Error");
349        assert_eq!(ErrorKind::BodyTransfer.as_str(), "Body Transfer Error");
350        assert_eq!(ErrorKind::Build.as_str(), "Build Error");
351        assert_eq!(ErrorKind::Connect.as_str(), "Connect Error");
352        assert_eq!(
353            ErrorKind::ConnectionUpgrade.as_str(),
354            "Connection Upgrade Error"
355        );
356        assert_eq!(ErrorKind::Other.as_str(), "Other Error");
357        assert_eq!(ErrorKind::Redirect.as_str(), "Redirect Error");
358        assert_eq!(ErrorKind::Request.as_str(), "Request Error");
359        assert_eq!(ErrorKind::Timeout.as_str(), "Timeout Error");
360        assert_eq!(ErrorKind::UserAborted.as_str(), "User Aborted Error");
361    }
362
363    /// UT test cases for `HttpClientError` error kind function.
364    ///
365    /// # Brief
366    /// 1. Calls `HttpClientError::user_aborted`, `HttpClientError::other`.
367    /// 2. Checks if the results are correct.
368    #[test]
369    fn ut_err_kind() {
370        let user_aborted = HttpClientError::user_aborted();
371        assert_eq!(user_aborted.error_kind(), ErrorKind::UserAborted);
372        let other = HttpClientError::other(user_aborted);
373        assert_eq!(other.error_kind(), ErrorKind::Other);
374    }
375
376    /// UT test cases for `HttpClientError::from_io_error` function.
377    ///
378    /// # Brief
379    /// 1. Calls `HttpClientError::from_io_error`.
380    /// 2. Checks if the results are correct.
381    #[test]
382    fn ut_err_from_io_error() {
383        let error_build =
384            HttpClientError::from_io_error(ErrorKind::Build, io::Error::from(io::ErrorKind::Other));
385        assert_eq!(error_build.error_kind(), ErrorKind::Build);
386    }
387
388    /// UT test cases for `HttpClientError::from_error` function.
389    ///
390    /// # Brief
391    /// 1. Calls `HttpClientError::from_error`.
392    /// 2. Checks if the results are correct.
393    #[test]
394    fn ut_err_from_error() {
395        let error_build = HttpClientError::from_error(
396            ErrorKind::Build,
397            HttpClientError::from_str(ErrorKind::Request, "test error"),
398        );
399        assert_eq!(error_build.error_kind(), ErrorKind::Build);
400    }
401
402    /// UT test cases for `HttpClientError::from_str` function.
403    ///
404    /// # Brief
405    /// 1. Calls `HttpClientError::from_str`.
406    /// 2. Checks if the results are correct.
407    #[test]
408    fn ut_err_from_str() {
409        let error_request = HttpClientError::from_str(ErrorKind::Request, "error");
410        assert_eq!(error_request.error_kind(), ErrorKind::Request);
411        let error_timeout = HttpClientError::from_str(ErrorKind::Timeout, "error");
412        assert_eq!(
413            format!("{:?}", error_timeout),
414            "HttpClientError { ErrorKind: Timeout, Cause: error }"
415        );
416        assert_eq!(format!("{error_timeout}"), "Timeout Error: error");
417    }
418
419    /// UT test cases for `HttpClientError::io_error` function.
420    ///
421    /// # Brief
422    /// 1. Calls `HttpClientError::io_error`.
423    /// 2. Checks if the results are correct.
424    #[test]
425    fn ut_client_err_io_error() {
426        let error = HttpClientError::from_io_error(
427            ErrorKind::Request,
428            io::Error::from(io::ErrorKind::BrokenPipe),
429        );
430        assert!(error.io_error().is_some());
431        let error = HttpClientError::from_str(ErrorKind::Request, "error");
432        assert!(error.io_error().is_none());
433    }
434
435    /// UT test cases for `Debug` of `HttpClientError`.
436    ///
437    /// # Brief
438    /// 1. Calls `HttpClientError::fmt`.
439    /// 2. Checks if the results are correct.
440    #[test]
441    fn ut_client_err_debug_fmt() {
442        let error = HttpClientError::from_io_error(
443            ErrorKind::Request,
444            io::Error::from(io::ErrorKind::BrokenPipe),
445        );
446        assert_eq!(
447            format!("{:?}", error),
448            "HttpClientError { ErrorKind: Request, Cause: Kind(BrokenPipe) }"
449        );
450
451        let error = HttpClientError::user_aborted();
452        assert_eq!(
453            format!("{:?}", error),
454            "HttpClientError { ErrorKind: UserAborted, Cause: No reason }"
455        );
456
457        let error = HttpClientError::other(io::Error::from(io::ErrorKind::BrokenPipe));
458        assert_eq!(
459            format!("{:?}", error),
460            "HttpClientError { ErrorKind: Other, Cause: Kind(BrokenPipe) }"
461        );
462    }
463
464    /// UT test cases for `Display` of `HttpClientError`.
465    ///
466    /// # Brief
467    /// 1. Calls `HttpClientError::fmt`.
468    /// 2. Checks if the results are correct.
469    #[test]
470    fn ut_client_err_display_fmt() {
471        let error = HttpClientError::from_io_error(
472            ErrorKind::Request,
473            io::Error::from(io::ErrorKind::BrokenPipe),
474        );
475        assert_eq!(format!("{}", error), "Request Error: broken pipe");
476
477        let error = HttpClientError::user_aborted();
478        assert_eq!(format!("{}", error), "User Aborted Error: No reason");
479
480        let error = HttpClientError::other(io::Error::from(io::ErrorKind::BrokenPipe));
481        assert_eq!(format!("{}", error), "Other Error: broken pipe");
482    }
483}
484