use std::error::Error as StdError;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::io;
use std::result::Result as StdResult;
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
source: Option<Box<dyn StdError + Send + Sync>>,
}
impl Error {
pub fn is_retryable(&self) -> bool {
matches!(self.kind, ErrorKind::Retryable { .. })
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub(crate) fn retryable<E>(source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
Self {
kind: ErrorKind::Retryable {
reason: RetryReason::Transient,
},
source: Some(Box::new(source)),
}
}
pub(crate) fn retryable_with_reason(reason: RetryReason) -> Self {
Self {
kind: ErrorKind::Retryable { reason },
source: None,
}
}
pub(crate) fn config(msg: impl Into<String>) -> Self {
Self {
kind: ErrorKind::InvalidConfig(msg.into()),
source: None,
}
}
pub(crate) fn internal(msg: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Internal(msg.into()),
source: None,
}
}
pub(crate) fn internal_with_source<E>(msg: impl Into<String>, source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
Self {
kind: ErrorKind::Internal(msg.into()),
source: Some(Box::new(source)),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(ref source) = self.source {
write!(f, ": {}", source)?;
}
Ok(())
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.source.as_ref().map(|s| s.as_ref() as _)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
Retryable { reason: RetryReason },
InvalidConfig(String),
Internal(String),
}
impl Display for ErrorKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ErrorKind::Retryable { reason } => write!(f, "retryable error: {}", reason),
ErrorKind::InvalidConfig(msg) => write!(f, "configuration error: {}", msg),
ErrorKind::Internal(msg) => write!(f, "internal error: {}", msg),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RetryReason {
NoLeader,
LeaderTransition,
Transient,
NodeStarting,
}
impl Display for RetryReason {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
RetryReason::NoLeader => write!(f, "no leader available"),
RetryReason::LeaderTransition => write!(f, "leader transition in progress"),
RetryReason::Transient => write!(f, "temporary failure"),
RetryReason::NodeStarting => write!(f, "target node is starting"),
}
}
}
pub type Result<T> = StdResult<T, Error>;
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ApiError {
#[error("cannot forward request: {0}")]
CannotForward(String),
#[error("forward to leader")]
ForwardToLeader { leader_id: Option<u64> },
}
impl ApiError {
pub(crate) fn is_retryable(&self) -> bool {
matches!(
self,
ApiError::CannotForward(_) | ApiError::ForwardToLeader { .. }
)
}
}
impl From<ApiError> for Error {
fn from(e: ApiError) -> Self {
if e.is_retryable() {
let reason = match &e {
ApiError::ForwardToLeader { .. } => RetryReason::LeaderTransition,
_ => RetryReason::Transient,
};
Self {
kind: ErrorKind::Retryable { reason },
source: None,
}
} else {
Self::internal(e.to_string())
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Self::retryable(e)
}
}
impl From<tonic::transport::Error> for Error {
fn from(e: tonic::transport::Error) -> Self {
Self::retryable(e)
}
}
impl From<tonic::Status> for Error {
fn from(status: tonic::Status) -> Self {
match status.code() {
tonic::Code::Unavailable => Self::retryable_with_reason(RetryReason::Transient),
tonic::Code::InvalidArgument => Self::config(status.message()),
_ => Self::internal(format!("gRPC error: {}", status)),
}
}
}
impl From<postcard::Error> for Error {
fn from(e: postcard::Error) -> Self {
Self::internal_with_source("serialization failed", e)
}
}
pub type RockRaftError = Error;
pub type RockRaftResult<T> = Result<T>;