1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use http::StatusCode;
use std::error::Error as StdError;
use std::result::Result as StdResult;
use std::fmt;
use url::Url;

/// A `Result` alias.
pub type Result<T> = StdResult<T, Error>;

pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;

#[derive(Debug)]
pub enum ErrorKind {
    Request,
    Encode,
    Decode,
    Status(StatusCode),
}

/// The errors that may occur when processing a request.
pub struct Error {
    inner: Box<Inner>,
}

struct Inner {
    kind: ErrorKind,
    source: Option<BoxError>,
    url: Option<Url>,
}

impl Error {
    pub(crate) fn new<E>(kind: ErrorKind, source: Option<E>) -> Error
        where
            E: Into<BoxError>,
    {
        Error {
            inner: Box::new(Inner {
                kind,
                source: source.map(Into::into),
                url: None,
            }),
        }
    }

    pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
        Error::new(ErrorKind::Decode, Some(e))
    }

    pub(crate) fn encode<E: Into<BoxError>>(e: E) -> Error {
        Error::new(ErrorKind::Encode, Some(e))
    }

    pub(crate) fn status(url: Url, status: StatusCode) -> Self {
        Error::new(ErrorKind::Status(status), None::<Error>).with_url(url)
    }

    pub(crate) fn with_url(mut self, url: Url) -> Self {
        self.inner.url = Some(url);
        self
    }

    pub fn is_request(&self) -> bool {
        matches!(self.inner.kind, ErrorKind::Request)
    }

    pub fn is_encode(&self) -> bool {
        matches!(self.inner.kind, ErrorKind::Encode)
    }

    pub fn is_decode(&self) -> bool {
        matches!(self.inner.kind, ErrorKind::Decode)
    }

    pub fn is_status(&self) -> bool {
        matches!(self.inner.kind, ErrorKind::Status(_))
    }
}

impl StdError for Error {
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
        self.inner.source.as_ref().map(|e| &**e as _)
    }
}

impl fmt::Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut builder = f.debug_struct("feignhttp::Error");

        builder.field("kind", &self.inner.kind);

        if let Some(ref url) = self.inner.url {
            builder.field("url", url);
        }

        if let Some(ref source) = self.inner.source {
            builder.field("source", source);
        }

        builder.finish()
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.inner.kind {
            ErrorKind::Request => f.write_str("error sending request")?,
            ErrorKind::Encode => f.write_str("error encoding request body")?,
            ErrorKind::Decode => f.write_str("error decoding response body")?,
            ErrorKind::Status(ref status_code) => {
                let prefix = if status_code.is_client_error() {
                    "HTTP status client error"
                } else {
                    debug_assert!(status_code.is_server_error());
                    "HTTP status server error"
                };
                write!(f, "{} ({})", prefix, status_code)?;
            }
        }

        if let Some(ref url) = self.inner.url {
            write!(f, " for url ({})", url.as_str())?;
        }

        if let Some(ref e) = self.inner.source {
            write!(f, ": {}", e)?;
        }

        Ok(())
    }
}