use std::error::Error as StdError;
use engula_api::server::v1::{GroupDesc, ReplicaDesc, RootDesc};
pub type Result<T> = std::result::Result<T, Error>;
pub type AppResult<T> = std::result::Result<T, AppError>;
#[derive(thiserror::Error, Debug)]
pub enum AppError {
#[error("{0} not found")]
NotFound(String),
#[error("{0} already exists")]
AlreadyExists(String),
#[error("invalid argument {0}")]
InvalidArgument(String),
#[error("deadline exceeded {0}")]
DeadlineExceeded(String),
#[error("internal {0}")]
Internal(Box<dyn StdError + Send + Sync + 'static>),
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("invalid argument {0}")]
InvalidArgument(String),
#[error("deadline exceeded {0}")]
DeadlineExceeded(String),
#[error("{0} already exists")]
AlreadyExists(String),
#[error("{0} not found")]
NotFound(String),
#[error("{0} is exhausted")]
ResourceExhausted(String),
#[error("group epoch not match")]
EpochNotMatch(GroupDesc),
#[error("group {0} not found")]
GroupNotFound(u64),
#[error("not root leader")]
NotRootLeader(RootDesc, u64, Option<ReplicaDesc>),
#[error("not leader of group {0}")]
NotLeader(
u64,
u64,
Option<ReplicaDesc>,
),
#[error("rpc {0}")]
Rpc(tonic::Status),
#[error("internal {0}")]
Internal(Box<dyn StdError + Send + Sync + 'static>),
}
impl From<tonic::Status> for Error {
fn from(status: tonic::Status) -> Self {
use engula_api::server::v1;
use prost::Message;
use tonic::Code;
match status.code() {
Code::Ok => panic!("invalid argument"),
Code::InvalidArgument => Error::InvalidArgument(status.message().into()),
Code::DeadlineExceeded => Error::DeadlineExceeded(status.message().into()),
Code::AlreadyExists => Error::AlreadyExists(status.message().into()),
Code::ResourceExhausted => Error::ResourceExhausted(status.message().into()),
Code::NotFound => Error::NotFound(status.message().into()),
Code::Internal => Error::Internal(status.message().into()),
Code::Unknown if !status.details().is_empty() => v1::Error::decode(status.details())
.map(Into::into)
.unwrap_or_else(|_| Error::Rpc(status)),
_ => Error::Rpc(status),
}
}
}
impl From<engula_api::server::v1::Error> for Error {
fn from(err: engula_api::server::v1::Error) -> Self {
use engula_api::server::v1::error_detail_union::Value;
use tonic::Status;
if err.details.is_empty() {
return Status::internal("ErrorDetails is empty").into();
}
let detail = &err.details[0];
let msg = detail.message.clone();
match detail.detail.as_ref().and_then(|u| u.value.clone()) {
Some(Value::GroupNotFound(v)) => Error::GroupNotFound(v.group_id),
Some(Value::NotLeader(v)) => Error::NotLeader(v.group_id, v.term, v.leader),
Some(Value::NotRoot(v)) => {
Error::NotRootLeader(v.root.unwrap_or_default(), v.term, v.leader)
}
Some(Value::NotMatch(v)) => Error::EpochNotMatch(v.descriptor.unwrap_or_default()),
Some(Value::StatusCode(v)) => Status::new(v.into(), msg).into(),
_ => Status::internal(format!("unknown error detail, msg: {msg}")).into(),
}
}
}
impl From<Error> for AppError {
fn from(err: Error) -> Self {
match err {
Error::InvalidArgument(v) => AppError::InvalidArgument(v),
Error::DeadlineExceeded(v) => AppError::DeadlineExceeded(v),
Error::NotFound(v) => AppError::NotFound(v),
Error::AlreadyExists(v) => AppError::AlreadyExists(v),
Error::Internal(v) => AppError::Internal(v),
Error::Rpc(status) => panic!("unknown error: {status:?}"),
Error::EpochNotMatch(_)
| Error::ResourceExhausted(_)
| Error::GroupNotFound(_)
| Error::NotRootLeader(..)
| Error::NotLeader(..) => unreachable!(),
}
}
}