use std::fmt;
use std::time::Duration;
use reqwest::StatusCode;
use uuid::Uuid;
use crate::protocol::AppendErrorDetails;
use crate::protocol::AppendState;
use crate::protocol::StatementErrorDetails;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
Unexpected,
ConfigInvalid,
AppendRowsFailed,
StatementFailed,
StatementCancelled,
CatalogFailed,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum WriteErrorReason {
Timeout,
}
impl fmt::Display for WriteErrorReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Timeout => "timeout",
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub struct WriteErrorDetails {
pub reason: WriteErrorReason,
pub outcome: AppendState,
}
impl ErrorKind {
pub fn into_static(self) -> &'static str {
self.into()
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.into_static())
}
}
impl From<ErrorKind> for &'static str {
fn from(v: ErrorKind) -> &'static str {
match v {
ErrorKind::Unexpected => "Unexpected",
ErrorKind::ConfigInvalid => "ConfigInvalid",
ErrorKind::AppendRowsFailed => "AppendRowsFailed",
ErrorKind::StatementFailed => "StatementFailed",
ErrorKind::StatementCancelled => "StatementCancelled",
ErrorKind::CatalogFailed => "CatalogFailed",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ErrorStatus {
Permanent,
Temporary,
Persistent,
}
#[derive(Debug, Default, Clone)]
struct HttpErrorMetadata {
status: Option<StatusCode>,
request_id: Option<String>,
retry_after: Option<Duration>,
retryable: Option<bool>,
}
#[derive(Debug, Clone)]
struct ApiErrorDetails {
code: Option<String>,
details: Option<serde_json::Value>,
}
#[derive(Debug, Default, Clone)]
struct StatementErrorMetadata {
id: Option<Uuid>,
details: Option<StatementErrorDetails>,
}
impl fmt::Display for ErrorStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorStatus::Permanent => write!(f, "permanent"),
ErrorStatus::Temporary => write!(f, "temporary"),
ErrorStatus::Persistent => write!(f, "persistent"),
}
}
}
#[derive(Clone)]
pub struct Error {
kind: ErrorKind,
message: String,
status: ErrorStatus,
context: Vec<(&'static str, String)>,
api_error: Option<Box<ApiErrorDetails>>,
append_details: Option<AppendErrorDetails>,
write_details: Option<WriteErrorDetails>,
statement_metadata: Option<Box<StatementErrorMetadata>>,
http_metadata: Option<Box<HttpErrorMetadata>>,
source: Option<std::sync::Arc<anyhow::Error>>,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({})", self.kind, self.status)?;
if !self.context.is_empty() {
write!(f, ", context: {{ ")?;
write!(
f,
"{}",
self.context
.iter()
.map(|(k, v)| format!("{k}: {v}"))
.collect::<Vec<_>>()
.join(", ")
)?;
write!(f, " }}")?;
}
if !self.message.is_empty() {
write!(f, " => {}", self.message)?;
}
if let Some(code) = self.code() {
write!(f, ", code: {code}")?;
}
if let Some(id) = self.statement_id() {
write!(f, ", statement_id: {id}")?;
}
if let Some(status) = self.http_status() {
write!(f, ", http_status: {}", status.as_u16())?;
}
if let Some(request_id) = self.request_id() {
write!(f, ", request_id: {request_id}")?;
}
if let Some(retry_after) = self.retry_after() {
write!(f, ", retry_after: {retry_after:?}")?;
}
if let Some(details) = self.write_details() {
write!(
f,
", write_reason: {}, write_outcome: {}",
details.reason, details.outcome
)?;
}
if let Some(source) = &self.source {
write!(f, ", source: {source}")?;
}
Ok(())
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
let mut de = f.debug_struct("Error");
de.field("kind", &self.kind);
de.field("message", &self.message);
de.field("api_error", &self.api_error);
de.field("status", &self.status);
de.field("context", &self.context);
de.field("append_details", &self.append_details);
de.field("write_details", &self.write_details);
de.field("statement_id", &self.statement_id());
de.field("statement_details", &self.statement_details());
de.field("http_metadata", &self.http_metadata);
de.field("source", &self.source);
return de.finish();
}
write!(f, "{} ({})", self.kind, self.status)?;
if !self.message.is_empty() {
write!(f, " => {}", self.message)?;
}
if let Some(code) = self.code() {
write!(f, ", code: {code}")?;
}
if let Some(id) = self.statement_id() {
write!(f, ", statement_id: {id}")?;
}
if let Some(status) = self.http_status() {
write!(f, ", http_status: {}", status.as_u16())?;
}
if let Some(request_id) = self.request_id() {
write!(f, ", request_id: {request_id}")?;
}
if let Some(retry_after) = self.retry_after() {
write!(f, ", retry_after: {retry_after:?}")?;
}
if let Some(details) = self.write_details() {
write!(
f,
", write_reason: {}, write_outcome: {}",
details.reason, details.outcome
)?;
}
writeln!(f)?;
if !self.context.is_empty() {
writeln!(f)?;
writeln!(f, "Context:")?;
for (k, v) in self.context.iter() {
writeln!(f, " {k}: {v}")?;
}
}
if let Some(source) = &self.source {
writeln!(f)?;
writeln!(f, "Source:")?;
writeln!(f, " {source:#}")?;
}
Ok(())
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_ref().map(|v| v.as_ref().as_ref())
}
}
impl Error {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
status: ErrorStatus::Permanent,
context: Vec::default(),
api_error: None,
append_details: None,
write_details: None,
statement_metadata: None,
http_metadata: None,
source: None,
}
}
pub fn with_context(mut self, key: &'static str, value: impl ToString) -> Self {
self.context.push((key, value.to_string()));
self
}
pub fn set_source(mut self, src: impl Into<anyhow::Error>) -> Self {
debug_assert!(self.source.is_none(), "the source error has been set");
self.source = Some(std::sync::Arc::new(src.into()));
self
}
pub(crate) fn set_write_timeout(mut self) -> Self {
self.write_details = Some(WriteErrorDetails {
reason: WriteErrorReason::Timeout,
outcome: AppendState::Unknown,
});
self
}
pub fn write_details(&self) -> Option<&WriteErrorDetails> {
self.write_details.as_ref()
}
pub fn append_details(&self) -> Option<&AppendErrorDetails> {
self.append_details.as_ref()
}
pub(crate) fn set_append_details(mut self, details: AppendErrorDetails) -> Self {
self.append_details = Some(details);
self
}
pub fn statement_details(&self) -> Option<&StatementErrorDetails> {
self.statement_metadata
.as_ref()
.and_then(|m| m.details.as_ref())
}
pub(crate) fn set_statement_details(mut self, details: StatementErrorDetails) -> Self {
self.statement_metadata
.get_or_insert_with(Default::default)
.details = Some(details);
self
}
pub fn statement_id(&self) -> Option<Uuid> {
self.statement_metadata.as_ref().and_then(|m| m.id)
}
pub(crate) fn set_statement_id(mut self, id: Uuid) -> Self {
self.statement_metadata
.get_or_insert_with(Default::default)
.id = Some(id);
self
}
pub(crate) fn set_http_status(mut self, status: StatusCode) -> Self {
self.http_metadata_mut().status = Some(status);
self
}
pub(crate) fn set_request_id(mut self, request_id: String) -> Self {
self.http_metadata_mut().request_id = Some(request_id);
self
}
pub(crate) fn set_retry_after(mut self, retry_after: Duration) -> Self {
self.http_metadata_mut().retry_after = Some(retry_after);
self
}
pub(crate) fn set_server_retryable(mut self, retryable: Option<bool>) -> Self {
self.http_metadata_mut().retryable = retryable;
self
}
pub(crate) fn server_retryable(&self) -> Option<bool> {
self.http_metadata
.as_ref()
.and_then(|metadata| metadata.retryable)
}
fn http_metadata_mut(&mut self) -> &mut HttpErrorMetadata {
self.http_metadata
.get_or_insert_with(|| Box::new(HttpErrorMetadata::default()))
}
pub fn set_permanent(mut self) -> Self {
self.status = ErrorStatus::Permanent;
self
}
pub fn set_temporary(mut self) -> Self {
self.status = ErrorStatus::Temporary;
self
}
pub fn set_persistent(mut self) -> Self {
self.status = ErrorStatus::Persistent;
self
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn code(&self) -> Option<&str> {
self.api_error
.as_deref()
.and_then(|e| e.code.as_deref())
.or_else(|| self.statement_details().map(|e| e.code.as_str()))
}
pub fn details(&self) -> Option<&serde_json::Value> {
self.api_error
.as_deref()
.and_then(|e| e.details.as_ref())
.or_else(|| self.statement_details().and_then(|e| e.details.as_ref()))
}
pub(crate) fn set_api_error(
mut self,
code: Option<String>,
details: Option<serde_json::Value>,
) -> Self {
if code.is_some() || details.is_some() {
self.api_error = Some(Box::new(ApiErrorDetails { code, details }));
}
self
}
pub fn message(&self) -> &str {
&self.message
}
pub fn http_status(&self) -> Option<StatusCode> {
self.http_metadata
.as_ref()
.and_then(|metadata| metadata.status)
}
pub fn request_id(&self) -> Option<&str> {
self.http_metadata
.as_ref()
.and_then(|metadata| metadata.request_id.as_deref())
}
pub fn retry_after(&self) -> Option<Duration> {
self.http_metadata
.as_ref()
.and_then(|metadata| metadata.retry_after)
}
pub fn is_retryable(&self) -> bool {
self.is_temporary()
}
pub fn is_temporary(&self) -> bool {
self.status == ErrorStatus::Temporary
}
pub fn is_permanent(&self) -> bool {
self.status == ErrorStatus::Permanent
}
pub fn is_persistent(&self) -> bool {
self.status == ErrorStatus::Persistent
}
}