use std::fmt;
use reqwest::Error as HttpClientError;
use reqwest::StatusCode;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
AuthenticationFailed,
AccessDenied,
ResourceNotFound,
TooManyItems,
EndpointNotFound,
InvalidInput,
IncompatibleApiVersion,
Conflict,
OperationTimedOut,
OperationFailed,
ProtocolError,
InvalidResponse,
InternalServerError,
InvalidConfig,
}
#[derive(Debug, Clone)]
pub struct Error {
kind: ErrorKind,
message: String,
status: Option<StatusCode>,
}
impl Error {
#[inline]
pub fn new<S: Into<String>>(kind: ErrorKind, message: S) -> Error {
Error {
kind,
message: message.into(),
status: None,
}
}
#[inline]
pub fn set_status(&mut self, status: StatusCode) {
self.status = Some(status);
}
#[inline]
pub fn with_status(mut self, status: StatusCode) -> Self {
self.set_status(status);
self
}
#[inline]
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub(crate) fn new_endpoint_not_found<D: fmt::Display>(service_type: D) -> Error {
Error::new(
ErrorKind::EndpointNotFound,
format!("Endpoint for service {} was not found", service_type),
)
}
}
impl ErrorKind {
#[allow(clippy::trivially_copy_pass_by_ref)]
#[inline]
pub fn description(&self) -> &'static str {
match self {
ErrorKind::AuthenticationFailed => "Failed to authenticate",
ErrorKind::AccessDenied => "Access to the resource is denied",
ErrorKind::ResourceNotFound => "Requested resource was not found",
ErrorKind::TooManyItems => "Request returned too many items",
ErrorKind::EndpointNotFound => "Requested endpoint was not found",
ErrorKind::InvalidInput => "Input value(s) are invalid or missing",
ErrorKind::IncompatibleApiVersion => "Incompatible or unsupported API version",
ErrorKind::Conflict => "Requested cannot be fulfilled due to a conflict",
ErrorKind::OperationTimedOut => "Time out reached while waiting for the operation",
ErrorKind::OperationFailed => "Requested operation has failed",
ErrorKind::ProtocolError => "Error when accessing the server",
ErrorKind::InvalidResponse => "Received invalid response",
ErrorKind::InternalServerError => "Internal server error or bad gateway",
ErrorKind::InvalidConfig => "configuration file cannot be found or is invalid",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.kind, self.message)
}
}
impl ::std::error::Error for Error {
fn description(&self) -> &str {
self.kind.description()
}
fn cause(&self) -> Option<&dyn ::std::error::Error> {
None
}
}
impl From<StatusCode> for ErrorKind {
fn from(value: StatusCode) -> ErrorKind {
match value {
StatusCode::UNAUTHORIZED => ErrorKind::AuthenticationFailed,
StatusCode::FORBIDDEN => ErrorKind::AccessDenied,
StatusCode::NOT_FOUND => ErrorKind::ResourceNotFound,
StatusCode::NOT_ACCEPTABLE => ErrorKind::IncompatibleApiVersion,
StatusCode::CONFLICT => ErrorKind::Conflict,
c if c.is_client_error() => ErrorKind::InvalidInput,
c if c.is_server_error() => ErrorKind::InternalServerError,
_ => ErrorKind::InvalidResponse,
}
}
}
impl From<HttpClientError> for Error {
fn from(value: HttpClientError) -> Error {
let msg = value.to_string();
let kind = if value.is_builder() {
ErrorKind::InvalidInput
} else {
value
.status()
.map(From::from)
.unwrap_or(ErrorKind::ProtocolError)
};
let error = Error::new(kind, msg);
if let Some(status) = value.status() {
error.with_status(status)
} else {
error
}
}
}
#[cfg(test)]
pub mod test {
use super::{Error, ErrorKind};
#[test]
fn test_error_display() {
let error = Error::new(ErrorKind::InvalidInput, "boom");
assert_eq!(error.kind(), ErrorKind::InvalidInput);
let s = format!("{}", error);
assert_eq!(&s, "Input value(s) are invalid or missing: boom");
}
}