use std::fmt::{Display, Formatter, Result as FmtResult};
use http::{HeaderName, StatusCode};
use url::ParseError as UrlParseError;
use crate::{query::QueryParamError, url::PathAndQueryError};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("error building request")]
Build(#[from] BuildError),
#[error("HTTP transport error")]
Transport(#[source] reqwest::Error),
#[error("HTTP status error ({0})")]
Status(StatusCode),
#[error("invalid or unexpected response body")]
Body(#[from] BodyError),
}
impl Error {
#[cold]
pub fn bad_header_name(err: impl Into<http::Error>) -> Self {
Self::Build(BuildError::HeaderName(err.into()))
}
#[cold]
pub fn bad_header_value(name: HeaderName, err: impl Into<http::Error>) -> Self {
Self::Build(BuildError::HeaderValue(name, err.into()))
}
pub fn category(&self) -> ErrorCategory {
match self {
Self::Build(_) => ErrorCategory::Build,
Self::Transport(err) if err.is_timeout() => ErrorCategory::Timeout,
Self::Transport(err) if err.is_connect() => ErrorCategory::Connect,
Self::Transport(_) => ErrorCategory::Transport,
&Self::Status(status) => ErrorCategory::Status(status),
Self::Body(_) => ErrorCategory::Body,
}
}
}
impl From<QueryParamError> for Error {
fn from(err: QueryParamError) -> Self {
Self::Build(BuildError::QueryParam(err))
}
}
impl From<PathAndQueryError> for Error {
fn from(err: PathAndQueryError) -> Self {
Self::Build(BuildError::Path(err))
}
}
impl From<UrlParseError> for Error {
fn from(err: UrlParseError) -> Self {
Self::Build(BuildError::Url(err))
}
}
impl From<reqwest::Error> for Error {
#[cold]
fn from(err: reqwest::Error) -> Self {
if err.is_builder() {
Self::Build(BuildError::Request(err))
} else if let Some(status) = err.status() {
Self::Status(status)
} else {
Self::Transport(err)
}
}
}
impl From<serde_json::Error> for Error {
#[cold]
fn from(err: serde_json::Error) -> Self {
Self::Body(BodyError::Json(err))
}
}
impl From<serde_path_to_error::Error<serde_json::Error>> for Error {
#[cold]
fn from(err: serde_path_to_error::Error<serde_json::Error>) -> Self {
Self::Body(BodyError::JsonWithPath(err))
}
}
#[derive(Debug, thiserror::Error)]
pub enum BuildError {
#[error("invalid URL")]
Url(#[source] UrlParseError),
#[error("invalid query parameter")]
QueryParam(#[source] QueryParamError),
#[error(transparent)]
Path(PathAndQueryError),
#[error("invalid header name")]
HeaderName(#[source] http::Error),
#[error("invalid value for header `{0}`")]
HeaderValue(HeaderName, #[source] http::Error),
#[error(transparent)]
Request(reqwest::Error),
}
#[derive(Debug, thiserror::Error)]
pub enum BodyError {
#[error(transparent)]
Json(serde_json::Error),
#[error(transparent)]
JsonWithPath(serde_path_to_error::Error<serde_json::Error>),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ErrorCategory {
Build,
Connect,
Timeout,
Transport,
Status(StatusCode),
Body,
}
impl Display for ErrorCategory {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(match self {
Self::Build => "build",
Self::Connect => "connect",
Self::Timeout => "timeout",
Self::Transport => "transport",
Self::Status(status) => status.as_str(),
Self::Body => "body",
})
}
}