use crate::errors::NeedRetry::IdempotentOnly;
use crate::grpc_wrapper::raw_errors::RawError;
use std::fmt::{Debug, Display, Formatter};
use std::sync::Arc;
use ydb_grpc::ydb_proto::status_ids::StatusCode;
pub type YdbResult<T> = std::result::Result<T, YdbError>;
pub type YdbResultWithCustomerErr<T> = std::result::Result<T, YdbOrCustomerError>;
#[derive(Clone)]
pub enum YdbOrCustomerError {
YDB(YdbError),
Customer(Arc<Box<dyn std::error::Error + Send + Sync>>),
}
impl YdbOrCustomerError {
#[allow(dead_code)]
pub(crate) fn from_mess<T: Into<String>>(s: T) -> Self {
Self::Customer(Arc::new(Box::new(YdbError::Custom(s.into()))))
}
#[allow(dead_code)]
pub(crate) fn from_err<T: std::error::Error + 'static + Send + Sync>(err: T) -> Self {
Self::Customer(Arc::new(Box::new(err)))
}
}
impl Debug for YdbOrCustomerError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::YDB(err) => Debug::fmt(err, f),
Self::Customer(err) => Debug::fmt(err, f),
}
}
}
impl Display for YdbOrCustomerError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::YDB(err) => Display::fmt(err, f),
Self::Customer(err) => Display::fmt(err, f),
}
}
}
impl std::error::Error for YdbOrCustomerError {}
impl From<YdbError> for YdbOrCustomerError {
fn from(e: YdbError) -> Self {
Self::YDB(e)
}
}
pub(crate) enum NeedRetry {
True, IdempotentOnly, False, }
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum YdbError {
Custom(String),
Convert(String),
NoRows,
InternalError(String),
TransportDial(Arc<tonic::transport::Error>),
Transport(String),
TransportGRPCStatus(Arc<tonic::Status>),
YdbStatusError(YdbStatusError),
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct YdbStatusError {
#[allow(dead_code)]
pub message: String,
pub operation_status: i32,
pub issues: Vec<YdbIssue>,
}
impl YdbStatusError {
pub fn operation_status(&self) -> YdbResult<StatusCode> {
StatusCode::from_i32(self.operation_status).ok_or_else(|| {
YdbError::InternalError(format!("unknown status code: {}", self.operation_status))
})
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
#[repr(u32)]
pub enum YdbIssueSeverity {
Fatal = 0,
Error = 1,
Warning = 2,
Info = 3,
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct YdbIssue {
pub issue_code: u32,
pub message: String,
pub issues: Vec<YdbIssue>,
pub severity: u32,
}
impl YdbIssue {
pub fn severity(&self) -> YdbResult<YdbIssueSeverity> {
let val = match self.severity {
0 => YdbIssueSeverity::Fatal,
1 => YdbIssueSeverity::Error,
2 => YdbIssueSeverity::Warning,
3 => YdbIssueSeverity::Info,
_ => {
return Err(YdbError::InternalError(format!(
"unexpected issue severity: {}",
self.severity
)))
}
};
Ok(val)
}
}
impl YdbError {
pub(crate) fn from_str<T: Into<String>>(s: T) -> YdbError {
YdbError::Custom(s.into())
}
pub(crate) fn need_retry(&self) -> NeedRetry {
match self {
Self::Convert(_) => NeedRetry::False,
Self::Custom(_) => NeedRetry::False,
Self::InternalError(_) => NeedRetry::False,
Self::NoRows => NeedRetry::False,
Self::TransportDial(_) => NeedRetry::True,
Self::Transport(_) => IdempotentOnly, Self::TransportGRPCStatus(status) => {
use tonic::Code;
match status.code() {
Code::Aborted | Code::ResourceExhausted => NeedRetry::True,
Code::Internal | Code::Cancelled | Code::Unavailable => {
NeedRetry::IdempotentOnly
}
_ => NeedRetry::False,
}
}
Self::YdbStatusError(ydb_err) => {
if let Some(status) = StatusCode::from_i32(ydb_err.operation_status) {
match status {
StatusCode::Aborted
| StatusCode::Unavailable
| StatusCode::Overloaded
| StatusCode::BadSession
| StatusCode::SessionBusy => NeedRetry::True,
StatusCode::Undetermined => NeedRetry::IdempotentOnly,
_ => NeedRetry::False,
}
} else {
NeedRetry::False
}
}
}
}
}
impl Display for YdbError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self::Debug::fmt(self, f)
}
}
macro_rules! to_custom_ydb_err {
($($t:ty),+) => {
$(
impl From<$t> for YdbError {
fn from(e: $t) -> Self {
return YdbError::Custom(e.to_string());
}
}
)+
};
}
impl std::error::Error for YdbError {}
to_custom_ydb_err!(
YdbOrCustomerError,
std::convert::Infallible,
http::Error,
reqwest::Error,
serde_json::Error,
std::env::VarError,
std::io::Error,
std::num::TryFromIntError,
std::string::FromUtf8Error,
std::time::SystemTimeError,
&str,
strum::ParseError,
tonic::transport::Error,
tokio::sync::AcquireError,
tokio::sync::oneshot::error::RecvError,
tokio::sync::watch::error::RecvError,
tokio::task::JoinError,
tonic::codegen::http::uri::InvalidUri,
url::ParseError
);
impl From<Box<dyn std::any::Any + Send>> for YdbError {
fn from(e: Box<dyn std::any::Any + Send>) -> Self {
YdbError::Custom(format!("{:?}", e))
}
}
impl<T> From<std::sync::PoisonError<T>> for YdbError {
fn from(e: std::sync::PoisonError<T>) -> Self {
YdbError::Custom(e.to_string())
}
}
impl From<tonic::Status> for YdbError {
fn from(e: tonic::Status) -> Self {
YdbError::TransportGRPCStatus(Arc::new(e))
}
}
impl From<RawError> for YdbError {
fn from(e: RawError) -> Self {
match e {
RawError::Custom(message) => YdbError::Custom(format!("raw custom error: {}", message)),
RawError::ProtobufDecodeError(message) => {
YdbError::Custom(format!("decode protobuf error: {}", message))
}
RawError::TonicStatus(s) => YdbError::TransportGRPCStatus(Arc::new(s)),
RawError::YdbStatus(status_error) => YdbError::YdbStatusError(status_error),
}
}
}