use http::Response;
use std::{
error::Error as StdError,
fmt, io,
net::SocketAddr,
sync::{Arc, OnceLock},
};
use crate::ResponseExt;
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum ErrorKind {
BadClientCertificate,
BadServerCertificate,
ClientInitialization,
ConnectionFailed,
InvalidContentEncoding,
InvalidCredentials,
InvalidRequest,
Io,
NameResolution,
ProtocolViolation,
RequestBodyNotRewindable,
Timeout,
TlsEngine,
TooManyRedirects,
#[doc(hidden)]
Unknown,
}
impl ErrorKind {
#[inline]
fn description(&self) -> Option<&str> {
match self {
Self::BadClientCertificate => Some("a problem occurred with the local certificate"),
Self::BadServerCertificate => Some("the server certificate could not be validated"),
Self::ClientInitialization => Some("failed to initialize client"),
Self::ConnectionFailed => Some("failed to connect to the server"),
Self::InvalidContentEncoding => Some(
"the server either returned a response using an unknown or unsupported encoding format, or the response encoding was malformed",
),
Self::InvalidCredentials => {
Some("provided authentication credentials were rejected by the server")
}
Self::InvalidRequest => Some("invalid HTTP request"),
Self::NameResolution => Some("failed to resolve host name"),
Self::ProtocolViolation => {
Some("the server made an unrecoverable HTTP protocol violation")
}
Self::RequestBodyNotRewindable => {
Some("request body could not be re-sent because it is not rewindable")
}
Self::Timeout => {
Some("request or operation took longer than the configured timeout time")
}
Self::TlsEngine => Some("error ocurred in the secure socket engine"),
Self::TooManyRedirects => Some("number of redirects hit the maximum amount"),
_ => None,
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.description().unwrap_or("unknown error"))
}
}
impl PartialEq<ErrorKind> for &'_ ErrorKind {
fn eq(&self, other: &ErrorKind) -> bool {
*self == other
}
}
#[derive(Clone)]
pub struct Error(Arc<Inner>);
struct Inner {
kind: ErrorKind,
context: Option<String>,
source: Option<Box<dyn SourceError>>,
local_addr: OnceLock<SocketAddr>,
remote_addr: OnceLock<SocketAddr>,
}
impl Error {
pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
Self::with_context(kind, None, source)
}
pub(crate) fn with_context<E>(kind: ErrorKind, context: Option<String>, source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
Self(Arc::new(Inner {
kind,
context,
source: Some(Box::new(source)),
local_addr: OnceLock::new(),
remote_addr: OnceLock::new(),
}))
}
pub(crate) fn with_response<B>(kind: ErrorKind, response: &Response<B>) -> Self {
let error = Self::from(kind);
if let Some(addr) = response.local_addr() {
let _ = error.0.local_addr.set(addr);
}
if let Some(addr) = response.remote_addr() {
let _ = error.0.remote_addr.set(addr);
}
error
}
pub(crate) fn from_any<E>(error: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
castaway::match_type!(error, {
Error as error => error,
std::io::Error as error => error.into(),
curl::Error as error => {
Self::with_context(
if error.is_ssl_certproblem() || error.is_ssl_cacert_badfile() {
ErrorKind::BadClientCertificate
} else if error.is_peer_failed_verification()
|| error.is_ssl_cacert()
|| error.is_ssl_cipher()
|| error.is_ssl_issuer_error()
{
ErrorKind::BadServerCertificate
} else if error.is_interface_failed() {
ErrorKind::ClientInitialization
} else if error.is_couldnt_connect() || error.is_ssl_connect_error() {
ErrorKind::ConnectionFailed
} else if error.is_bad_content_encoding() || error.is_conv_failed() {
ErrorKind::InvalidContentEncoding
} else if error.is_login_denied() {
ErrorKind::InvalidCredentials
} else if error.is_url_malformed() {
ErrorKind::InvalidRequest
} else if error.is_couldnt_resolve_host() || error.is_couldnt_resolve_proxy() {
ErrorKind::NameResolution
} else if error.is_got_nothing()
|| error.is_http2_error()
|| error.is_http2_stream_error()
|| error.is_unsupported_protocol()
|| error.code() == curl_sys::CURLE_FTP_WEIRD_SERVER_REPLY
{
ErrorKind::ProtocolViolation
} else if error.is_send_error()
|| error.is_recv_error()
|| error.is_read_error()
|| error.is_write_error()
|| error.is_upload_failed()
|| error.is_send_fail_rewind()
|| error.is_aborted_by_callback()
|| error.is_partial_file()
{
ErrorKind::Io
} else if error.is_ssl_engine_initfailed()
|| error.is_ssl_engine_notfound()
|| error.is_ssl_engine_setfailed()
{
ErrorKind::TlsEngine
} else if error.is_operation_timedout() {
ErrorKind::Timeout
} else if error.is_too_many_redirects() {
ErrorKind::TooManyRedirects
} else {
ErrorKind::Unknown
},
error.extra_description().map(String::from),
error,
)
},
curl::MultiError as error => {
Self::new(
if error.is_bad_socket() {
ErrorKind::Io
} else {
ErrorKind::Unknown
},
error,
)
},
error => Error::new(ErrorKind::Unknown, error),
})
}
#[inline]
pub fn kind(&self) -> &ErrorKind {
&self.0.kind
}
pub fn is_client(&self) -> bool {
match self.kind() {
ErrorKind::BadClientCertificate
| ErrorKind::ClientInitialization
| ErrorKind::InvalidCredentials
| ErrorKind::InvalidRequest
| ErrorKind::RequestBodyNotRewindable
| ErrorKind::TlsEngine => true,
_ => false,
}
}
pub fn is_network(&self) -> bool {
match self.kind() {
ErrorKind::ConnectionFailed | ErrorKind::Io | ErrorKind::NameResolution => true,
_ => false,
}
}
pub fn is_server(&self) -> bool {
match self.kind() {
ErrorKind::BadServerCertificate
| ErrorKind::ProtocolViolation
| ErrorKind::TooManyRedirects => true,
_ => false,
}
}
pub fn is_timeout(&self) -> bool {
self.kind() == ErrorKind::Timeout
}
pub fn is_tls(&self) -> bool {
match self.kind() {
ErrorKind::BadClientCertificate
| ErrorKind::BadServerCertificate
| ErrorKind::TlsEngine => true,
_ => false,
}
}
pub fn local_addr(&self) -> Option<SocketAddr> {
self.0.local_addr.get().cloned()
}
pub fn remote_addr(&self) -> Option<SocketAddr> {
self.0.remote_addr.get().cloned()
}
pub(crate) fn with_local_addr(self, addr: SocketAddr) -> Self {
let _ = self.0.local_addr.set(addr);
self
}
pub(crate) fn with_remote_addr(self, addr: SocketAddr) -> Self {
let _ = self.0.remote_addr.set(addr);
self
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.0.source.as_ref().map(|source| source.as_dyn_error())
}
}
impl PartialEq<ErrorKind> for Error {
fn eq(&self, kind: &ErrorKind) -> bool {
self.kind().eq(kind)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Error")
.field("kind", &self.kind())
.field("context", &self.0.context)
.field("source", &self.source())
.field(
"source_type",
&self.0.source.as_ref().map(|e| e.type_name()),
)
.field("local_addr", &self.0.local_addr.get())
.field("remote_addr", &self.0.remote_addr.get())
.finish()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(s) = self.0.context.as_ref() {
write!(f, "{}: {}", self.kind(), s)
} else {
write!(f, "{}", self.kind())
}
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Self {
Self(Arc::new(Inner {
kind,
context: None,
source: None,
local_addr: OnceLock::new(),
remote_addr: OnceLock::new(),
}))
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
if let Some(inner) = error.get_ref() {
if inner.is::<Self>() {
return *error.into_inner().unwrap().downcast().unwrap();
}
}
Self::new(
match error.kind() {
io::ErrorKind::ConnectionRefused => ErrorKind::ConnectionFailed,
io::ErrorKind::TimedOut => ErrorKind::Timeout,
_ => ErrorKind::Io,
},
error,
)
}
}
impl From<Error> for io::Error {
fn from(error: Error) -> Self {
let kind = match error.kind() {
ErrorKind::ConnectionFailed => io::ErrorKind::ConnectionRefused,
ErrorKind::Timeout => io::ErrorKind::TimedOut,
_ => io::ErrorKind::Other,
};
Self::new(kind, error)
}
}
impl From<http::Error> for Error {
fn from(error: http::Error) -> Error {
Self::new(
if error.is::<http::header::InvalidHeaderName>()
|| error.is::<http::header::InvalidHeaderValue>()
|| error.is::<http::method::InvalidMethod>()
|| error.is::<http::uri::InvalidUri>()
|| error.is::<http::uri::InvalidUriParts>()
{
ErrorKind::InvalidRequest
} else {
ErrorKind::Unknown
},
error,
)
}
}
trait SourceError: StdError + Send + Sync + 'static {
fn type_name(&self) -> &'static str;
fn as_dyn_error(&self) -> &(dyn StdError + 'static);
}
impl<T: StdError + Send + Sync + 'static> SourceError for T {
fn type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn as_dyn_error(&self) -> &(dyn StdError + 'static) {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
static_assertions::assert_impl_all!(Error: Send, Sync);
}