flawless-http 1.0.0-beta.3

HTTP client for https://flawless.dev.
Documentation
use crate::response::Response;

/// A request error.
///
/// There are two types of errors that can happen when a request is performed:
///
/// 1. **Status code errors**
///
/// Every response with a status code >= 400 is considered an error. In this case, the error `enum` will
/// contain the full [`Response`].
///
/// 2. **Transport protocol errors**
///
/// This kind of error happens in cases when there is a connection problem (e.g. connection refused) or a
/// response can't be parsed.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
    /// A response was successfully received but had status code >= 400. Values are (status_code, Response).
    Status(u16, Response),
    /// There was an error making the request or receiving the response.
    Transport(Transport),
}

impl Error {
    /// Error kind.
    ///
    /// If the response status code is >=400, the error will be of kind `ErrorKind::StatusCode`. All other
    /// kinds describe a transport protocol error.
    ///
    /// ```no_run
    /// use flawless_http::{get, ErrorKind};
    ///
    /// let response = get("http://httpbin.org/status/500").send();
    /// assert_eq!(response.unwrap_err().kind(), ErrorKind::StatusCode);
    /// ````
    pub fn kind(&self) -> ErrorKind {
        match self {
            Error::Status(_, _) => ErrorKind::StatusCode,
            Error::Transport(Transport { kind: k, .. }) => *k,
        }
    }

    /// Contains an error message providing more information about the error, if available.
    ///
    /// # Status code error message:
    ///
    /// ```no_run
    /// use flawless_http::get;
    ///
    /// let response = get("http://httpbin.org/status/500").send();
    /// assert_eq!(
    ///   response.unwrap_err().message(),
    ///   Some("Server returned status code 500".to_owned())
    /// );
    /// ````
    ///
    /// # Timeout error message:
    ///
    /// ```no_run
    /// use std::time::Duration;
    /// use flawless_http::get;
    ///
    /// let response = get("http://httpbin.org/delay/2")
    ///                  .timeout(Duration::from_secs(1))
    ///                  .send();
    /// let err_msg = response.unwrap_err().message().unwrap();
    /// assert!(err_msg.contains("timed out"));
    /// ````
    pub fn message(&self) -> Option<String> {
        match self {
            Error::Status(code, _) => Some(format!("Server returned status code {code}")),
            Error::Transport(Transport { message: m, .. }) => m.clone(),
        }
    }
}

impl From<flawless_wasabi::HttpError> for Error {
    fn from(value: flawless_wasabi::HttpError) -> Self {
        match value {
            flawless_wasabi::HttpError::Status(code, res) => Self::Status(code, res.into()),
            flawless_wasabi::HttpError::Transport(transport) => Self::Transport(transport.into()),
        }
    }
}

/// Error indicating an issue with the underlying request transport.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Transport {
    pub(crate) kind: ErrorKind,
    pub(crate) message: Option<String>,
    pub(crate) url: Option<String>,
}

impl Transport {
    /// The type of error that happened while processing the request.
    pub fn kind(&self) -> ErrorKind {
        self.kind
    }

    /// Higher level error details, if there are any.
    pub fn message(&self) -> Option<&str> {
        self.message.as_deref()
    }

    /// The `url` that failed. In cases of redirects, the original `url` might work, but one of the later
    /// redirects might fail.
    pub fn url(&self) -> Option<&String> {
        self.url.as_ref()
    }
}

impl From<flawless_wasabi::HttpTransportError> for Transport {
    fn from(value: flawless_wasabi::HttpTransportError) -> Self {
        Self { kind: value.kind.into(), message: value.message, url: value.url }
    }
}

/// The kind of error that happened when a request is performed.
///
/// In case of a statues code error (>= 400), the kind is going to be `StatusCode`. All other kinds are
/// related to the transport.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
    /// The `url` could not be understood.
    InvalidUrl,
    /// The `url` scheme could not be understood.
    UnknownScheme,
    /// DNS lookup failed.
    Dns,
    /// Connection to server failed.
    ConnectionFailed,
    /// Too many redirects.
    TooManyRedirects,
    /// A status line we don't understand (e.g. `HTTP/1.1 200 OK`).
    BadStatus,
    /// A header line that couldn't be parsed.
    BadHeader,
    /// Some unspecified `std::io::Error`.
    Io,
    /// There was an external failure before the request could finish.
    RequestInterrupted,
    /// HTTP status code indicating an error (e.g. 4xx, 5xx)
    /// Read the inner response body for details.
    StatusCode,
}

impl From<flawless_wasabi::HttpErrorKind> for ErrorKind {
    fn from(value: flawless_wasabi::HttpErrorKind) -> Self {
        match value {
            flawless_wasabi::HttpErrorKind::InvalidUrl => Self::InvalidUrl,
            flawless_wasabi::HttpErrorKind::UnknownScheme => Self::UnknownScheme,
            flawless_wasabi::HttpErrorKind::Dns => Self::Dns,
            flawless_wasabi::HttpErrorKind::ConnectionFailed => Self::ConnectionFailed,
            flawless_wasabi::HttpErrorKind::TooManyRedirects => Self::TooManyRedirects,
            flawless_wasabi::HttpErrorKind::BadStatus => Self::BadStatus,
            flawless_wasabi::HttpErrorKind::BadHeader => Self::BadHeader,
            flawless_wasabi::HttpErrorKind::RequestInterrupted => Self::RequestInterrupted,
            flawless_wasabi::HttpErrorKind::Io => Self::Io,
            flawless_wasabi::HttpErrorKind::StatusCode => Self::StatusCode,
        }
    }
}