bouillon 0.2.1

A thin, opinionated wrapper around soup that provides an easy, fluent API for sending HTTP requests.
Documentation
use crate::utils::{is_client_error, is_server_error};
use soup::{Message, Status, glib};
use std::{error, fmt, io};

/// Errors returned by this crate.
#[derive(Debug)]
pub struct Error {
    inner: Inner,
    uri: Option<glib::Uri>,
}

/// Result returned by this crate.
pub type Result<T> = std::result::Result<T, Error>;

impl Error {
    /// Returns the URL associated with this error.
    pub fn url(&self) -> Option<&glib::Uri> {
        self.uri.as_ref()
    }

    /// Strips the URL from this error if present.
    pub fn without_url(self) -> Self {
        Self {
            inner: self.inner,
            uri: None,
        }
    }

    /// Returns the status if this error was caused by
    /// [`Response::error_for_status`](`crate::Response::error_for_status`)
    pub fn status(&self) -> Option<Status> {
        if let Inner::Status(status, ..) = self.inner {
            Some(status)
        } else {
            None
        }
    }

    /// Returns the status code if this error was caused by
    /// [`Response::error_for_status`](`crate::Response::error_for_status`)
    pub fn status_code(&self) -> Option<u32> {
        if let Inner::Status(_, status_code, ..) = self.inner {
            Some(status_code)
        } else {
            None
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(uri) = &self.uri {
            write!(f, "Request to {uri} failed: {}", self.inner)
        } else {
            self.inner.fmt(f)
        }
    }
}

impl error::Error for Error {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match &self.inner {
            Inner::Glib(error) => Some(error),
            Inner::Io(error) => Some(error),
            Inner::Status(..) => None,
            #[cfg(feature = "json")]
            Inner::Json(error) => Some(error),
            #[cfg(any(feature = "form", feature = "query"))]
            Inner::Form(error) => Some(error),
        }
    }
}

impl Error {
    pub(crate) fn new(inner: impl Into<Inner>) -> Self {
        Self::new_with_uri(inner, None)
    }

    pub(crate) fn new_with_uri(inner: impl Into<Inner>, uri: Option<glib::Uri>) -> Self {
        Self {
            inner: inner.into(),
            uri,
        }
    }

    pub(crate) fn new_status(message: &Message) -> Self {
        Self::new_with_uri(
            Inner::Status(
                message.status(),
                message.status_code(),
                message.reason_phrase(),
            ),
            message.uri(),
        )
    }
}

#[derive(Debug)]
pub(crate) enum Inner {
    Glib(glib::Error),
    Io(io::Error),
    Status(Status, u32, Option<glib::GString>),
    #[cfg(feature = "json")]
    Json(json::Error),
    #[cfg(any(feature = "form", feature = "query"))]
    Form(serde_urlencoded::ser::Error),
}

impl fmt::Display for Inner {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Glib(error) => error.fmt(f),
            Self::Io(error) => error.fmt(f),
            Self::Status(_, code, reason) => {
                let prefix = if is_client_error(*code) {
                    "HTTP status client error"
                } else {
                    debug_assert!(is_server_error(*code));
                    "HTTP status server error"
                };
                if let Some(reason) = reason {
                    write!(f, "{prefix} ({code} {reason})")
                } else {
                    write!(f, "{prefix} ({code})")
                }
            }
            #[cfg(feature = "json")]
            Self::Json(error) => error.fmt(f),
            #[cfg(any(feature = "form", feature = "query"))]
            Self::Form(error) => error.fmt(f),
        }
    }
}

impl From<glib::Error> for Inner {
    fn from(value: glib::Error) -> Self {
        Inner::Glib(value)
    }
}

impl From<io::Error> for Inner {
    fn from(value: io::Error) -> Self {
        Inner::Io(value)
    }
}

#[cfg(feature = "json")]
impl From<json::Error> for Inner {
    fn from(value: json::Error) -> Self {
        Inner::Json(value)
    }
}

#[cfg(any(feature = "form", feature = "query"))]
impl From<serde_urlencoded::ser::Error> for Inner {
    fn from(value: serde_urlencoded::ser::Error) -> Self {
        Inner::Form(value)
    }
}