use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
Unauthorized,
Forbidden,
NotFound,
InvalidArgument,
SchemaViolation,
RateLimited,
Unavailable,
Timeout,
Internal,
Cancelled,
CircuitOpen,
Connection,
Protocol,
Configuration,
Unknown,
Conflict,
Transport,
InvalidResponse,
}
impl ErrorKind {
#[inline]
pub fn is_retriable(&self) -> bool {
matches!(
self,
ErrorKind::Unavailable
| ErrorKind::Timeout
| ErrorKind::RateLimited
| ErrorKind::CircuitOpen
| ErrorKind::Connection
)
}
#[inline]
pub fn http_status_code(&self) -> u16 {
match self {
ErrorKind::Unauthorized => 401,
ErrorKind::Forbidden => 403,
ErrorKind::NotFound => 404,
ErrorKind::InvalidArgument | ErrorKind::SchemaViolation => 400,
ErrorKind::Conflict => 409,
ErrorKind::RateLimited => 429,
ErrorKind::Timeout => 504,
ErrorKind::Unavailable => 503,
ErrorKind::Internal => 500,
ErrorKind::Cancelled => 499, ErrorKind::CircuitOpen => 503,
ErrorKind::Connection => 502,
ErrorKind::Protocol | ErrorKind::Transport => 502,
ErrorKind::Configuration => 500,
ErrorKind::InvalidResponse => 502,
ErrorKind::Unknown => 500,
}
}
pub fn from_http_status(status: u16) -> Self {
match status {
400 => ErrorKind::InvalidArgument,
401 => ErrorKind::Unauthorized,
403 => ErrorKind::Forbidden,
404 => ErrorKind::NotFound,
409 => ErrorKind::Conflict,
429 => ErrorKind::RateLimited,
499 => ErrorKind::Cancelled,
500 => ErrorKind::Internal,
502 => ErrorKind::Protocol,
503 => ErrorKind::Unavailable,
504 => ErrorKind::Timeout,
_ if (400..500).contains(&status) => ErrorKind::InvalidArgument,
_ if status >= 500 => ErrorKind::Internal,
_ => ErrorKind::Unknown,
}
}
#[cfg(feature = "grpc")]
pub fn from_grpc_code(code: tonic::Code) -> Self {
use tonic::Code;
match code {
Code::Ok => ErrorKind::Unknown, Code::Cancelled => ErrorKind::Cancelled,
Code::Unknown => ErrorKind::Unknown,
Code::InvalidArgument => ErrorKind::InvalidArgument,
Code::DeadlineExceeded => ErrorKind::Timeout,
Code::NotFound => ErrorKind::NotFound,
Code::AlreadyExists => ErrorKind::Conflict,
Code::PermissionDenied => ErrorKind::Forbidden,
Code::ResourceExhausted => ErrorKind::RateLimited,
Code::FailedPrecondition => ErrorKind::SchemaViolation,
Code::Aborted => ErrorKind::Conflict,
Code::OutOfRange => ErrorKind::InvalidArgument,
Code::Unimplemented => ErrorKind::Protocol,
Code::Internal => ErrorKind::Internal,
Code::Unavailable => ErrorKind::Unavailable,
Code::DataLoss => ErrorKind::Internal,
Code::Unauthenticated => ErrorKind::Unauthorized,
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorKind::Unauthorized => write!(f, "unauthorized"),
ErrorKind::Forbidden => write!(f, "forbidden"),
ErrorKind::NotFound => write!(f, "not found"),
ErrorKind::InvalidArgument => write!(f, "invalid argument"),
ErrorKind::SchemaViolation => write!(f, "schema violation"),
ErrorKind::RateLimited => write!(f, "rate limited"),
ErrorKind::Unavailable => write!(f, "service unavailable"),
ErrorKind::Timeout => write!(f, "timeout"),
ErrorKind::Internal => write!(f, "internal error"),
ErrorKind::Cancelled => write!(f, "cancelled"),
ErrorKind::CircuitOpen => write!(f, "circuit breaker open"),
ErrorKind::Connection => write!(f, "connection error"),
ErrorKind::Protocol => write!(f, "protocol error"),
ErrorKind::Configuration => write!(f, "configuration error"),
ErrorKind::Unknown => write!(f, "unknown error"),
ErrorKind::Conflict => write!(f, "conflict"),
ErrorKind::Transport => write!(f, "transport error"),
ErrorKind::InvalidResponse => write!(f, "invalid response"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_retriable() {
assert!(ErrorKind::Unavailable.is_retriable());
assert!(ErrorKind::Timeout.is_retriable());
assert!(ErrorKind::RateLimited.is_retriable());
assert!(ErrorKind::CircuitOpen.is_retriable());
assert!(ErrorKind::Connection.is_retriable());
assert!(!ErrorKind::Unauthorized.is_retriable());
assert!(!ErrorKind::Forbidden.is_retriable());
assert!(!ErrorKind::NotFound.is_retriable());
assert!(!ErrorKind::InvalidArgument.is_retriable());
assert!(!ErrorKind::SchemaViolation.is_retriable());
assert!(!ErrorKind::Internal.is_retriable());
assert!(!ErrorKind::Cancelled.is_retriable());
assert!(!ErrorKind::Protocol.is_retriable());
assert!(!ErrorKind::Configuration.is_retriable());
assert!(!ErrorKind::Unknown.is_retriable());
}
#[test]
fn test_http_status_code() {
assert_eq!(ErrorKind::Unauthorized.http_status_code(), 401);
assert_eq!(ErrorKind::Forbidden.http_status_code(), 403);
assert_eq!(ErrorKind::NotFound.http_status_code(), 404);
assert_eq!(ErrorKind::InvalidArgument.http_status_code(), 400);
assert_eq!(ErrorKind::SchemaViolation.http_status_code(), 400);
assert_eq!(ErrorKind::RateLimited.http_status_code(), 429);
assert_eq!(ErrorKind::Unavailable.http_status_code(), 503);
assert_eq!(ErrorKind::Timeout.http_status_code(), 504);
assert_eq!(ErrorKind::Internal.http_status_code(), 500);
assert_eq!(ErrorKind::Cancelled.http_status_code(), 499);
assert_eq!(ErrorKind::CircuitOpen.http_status_code(), 503);
assert_eq!(ErrorKind::Connection.http_status_code(), 502);
assert_eq!(ErrorKind::Protocol.http_status_code(), 502);
assert_eq!(ErrorKind::Configuration.http_status_code(), 500);
assert_eq!(ErrorKind::Unknown.http_status_code(), 500);
}
#[test]
fn test_from_http_status() {
assert_eq!(ErrorKind::from_http_status(400), ErrorKind::InvalidArgument);
assert_eq!(ErrorKind::from_http_status(401), ErrorKind::Unauthorized);
assert_eq!(ErrorKind::from_http_status(403), ErrorKind::Forbidden);
assert_eq!(ErrorKind::from_http_status(404), ErrorKind::NotFound);
assert_eq!(ErrorKind::from_http_status(429), ErrorKind::RateLimited);
assert_eq!(ErrorKind::from_http_status(499), ErrorKind::Cancelled);
assert_eq!(ErrorKind::from_http_status(500), ErrorKind::Internal);
assert_eq!(ErrorKind::from_http_status(502), ErrorKind::Protocol);
assert_eq!(ErrorKind::from_http_status(503), ErrorKind::Unavailable);
assert_eq!(ErrorKind::from_http_status(504), ErrorKind::Timeout);
assert_eq!(ErrorKind::from_http_status(405), ErrorKind::InvalidArgument);
assert_eq!(ErrorKind::from_http_status(422), ErrorKind::InvalidArgument);
assert_eq!(ErrorKind::from_http_status(451), ErrorKind::InvalidArgument);
assert_eq!(ErrorKind::from_http_status(501), ErrorKind::Internal);
assert_eq!(ErrorKind::from_http_status(505), ErrorKind::Internal);
assert_eq!(ErrorKind::from_http_status(200), ErrorKind::Unknown);
assert_eq!(ErrorKind::from_http_status(301), ErrorKind::Unknown);
}
#[test]
fn test_display() {
assert_eq!(format!("{}", ErrorKind::Unauthorized), "unauthorized");
assert_eq!(format!("{}", ErrorKind::Forbidden), "forbidden");
assert_eq!(format!("{}", ErrorKind::NotFound), "not found");
assert_eq!(
format!("{}", ErrorKind::InvalidArgument),
"invalid argument"
);
assert_eq!(
format!("{}", ErrorKind::SchemaViolation),
"schema violation"
);
assert_eq!(format!("{}", ErrorKind::RateLimited), "rate limited");
assert_eq!(format!("{}", ErrorKind::Unavailable), "service unavailable");
assert_eq!(format!("{}", ErrorKind::Timeout), "timeout");
assert_eq!(format!("{}", ErrorKind::Internal), "internal error");
assert_eq!(format!("{}", ErrorKind::Cancelled), "cancelled");
assert_eq!(
format!("{}", ErrorKind::CircuitOpen),
"circuit breaker open"
);
assert_eq!(format!("{}", ErrorKind::Connection), "connection error");
assert_eq!(format!("{}", ErrorKind::Protocol), "protocol error");
assert_eq!(
format!("{}", ErrorKind::Configuration),
"configuration error"
);
assert_eq!(format!("{}", ErrorKind::Unknown), "unknown error");
}
#[test]
fn test_error_kind_clone_and_eq() {
let kind = ErrorKind::Timeout;
let cloned = kind;
assert_eq!(kind, cloned);
}
#[test]
fn test_error_kind_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(ErrorKind::Timeout);
set.insert(ErrorKind::Unavailable);
set.insert(ErrorKind::Timeout); assert_eq!(set.len(), 2);
}
#[test]
fn test_is_retriable_remaining() {
assert!(!ErrorKind::Conflict.is_retriable());
assert!(!ErrorKind::Transport.is_retriable());
assert!(!ErrorKind::InvalidResponse.is_retriable());
}
#[test]
fn test_http_status_code_remaining() {
assert_eq!(ErrorKind::Conflict.http_status_code(), 409);
assert_eq!(ErrorKind::Transport.http_status_code(), 502);
assert_eq!(ErrorKind::InvalidResponse.http_status_code(), 502);
}
#[test]
fn test_display_remaining() {
assert_eq!(format!("{}", ErrorKind::Conflict), "conflict");
assert_eq!(format!("{}", ErrorKind::Transport), "transport error");
assert_eq!(
format!("{}", ErrorKind::InvalidResponse),
"invalid response"
);
}
#[test]
fn test_error_kind_debug() {
let kind = ErrorKind::Timeout;
let debug = format!("{:?}", kind);
assert!(debug.contains("Timeout"));
}
#[test]
fn test_from_http_status_conflict() {
assert_eq!(ErrorKind::from_http_status(409), ErrorKind::Conflict);
}
#[cfg(feature = "grpc")]
#[test]
fn test_from_grpc_code() {
use tonic::Code;
assert_eq!(ErrorKind::from_grpc_code(Code::Ok), ErrorKind::Unknown);
assert_eq!(
ErrorKind::from_grpc_code(Code::Cancelled),
ErrorKind::Cancelled
);
assert_eq!(ErrorKind::from_grpc_code(Code::Unknown), ErrorKind::Unknown);
assert_eq!(
ErrorKind::from_grpc_code(Code::InvalidArgument),
ErrorKind::InvalidArgument
);
assert_eq!(
ErrorKind::from_grpc_code(Code::DeadlineExceeded),
ErrorKind::Timeout
);
assert_eq!(
ErrorKind::from_grpc_code(Code::NotFound),
ErrorKind::NotFound
);
assert_eq!(
ErrorKind::from_grpc_code(Code::AlreadyExists),
ErrorKind::Conflict
);
assert_eq!(
ErrorKind::from_grpc_code(Code::PermissionDenied),
ErrorKind::Forbidden
);
assert_eq!(
ErrorKind::from_grpc_code(Code::ResourceExhausted),
ErrorKind::RateLimited
);
assert_eq!(
ErrorKind::from_grpc_code(Code::FailedPrecondition),
ErrorKind::SchemaViolation
);
assert_eq!(
ErrorKind::from_grpc_code(Code::Aborted),
ErrorKind::Conflict
);
assert_eq!(
ErrorKind::from_grpc_code(Code::OutOfRange),
ErrorKind::InvalidArgument
);
assert_eq!(
ErrorKind::from_grpc_code(Code::Unimplemented),
ErrorKind::Protocol
);
assert_eq!(
ErrorKind::from_grpc_code(Code::Internal),
ErrorKind::Internal
);
assert_eq!(
ErrorKind::from_grpc_code(Code::Unavailable),
ErrorKind::Unavailable
);
assert_eq!(
ErrorKind::from_grpc_code(Code::DataLoss),
ErrorKind::Internal
);
assert_eq!(
ErrorKind::from_grpc_code(Code::Unauthenticated),
ErrorKind::Unauthorized
);
}
}