use std::time::Duration;
use autumn_web::AutumnError;
use http::StatusCode;
use crate::config::ConfigError;
#[derive(Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum ClickHouseError {
#[error(transparent)]
Config(#[from] ConfigError),
#[error("ClickHouse refused the call: {kind}")]
#[non_exhaustive]
Database {
kind: ErrorKind,
detail: String,
},
#[error("the call did not complete in {timeout:?}")]
#[non_exhaustive]
Timeout {
timeout: Duration,
},
#[error("the migration `{name}` failed")]
#[non_exhaustive]
Migration {
name: String,
detail: String,
},
#[error("the ClickHouse plugin is not installed: add `ClickHousePlugin` to the app")]
NotInstalled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
InvalidParams,
Network,
TimedOut,
BadResponse,
NotFound,
SchemaMismatch,
Decode,
Unsupported,
Custom,
Unknown,
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::InvalidParams => "invalid parameters",
Self::Network => "network error",
Self::TimedOut => "timed out",
Self::BadResponse => "bad response",
Self::NotFound => "row not found",
Self::SchemaMismatch => "schema mismatch",
Self::Decode => "decode error",
Self::Unsupported => "unsupported",
Self::Custom => "client error",
Self::Unknown => "unknown error",
})
}
}
impl std::fmt::Debug for ClickHouseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ClickHouseError")
.field(&self.to_string())
.finish()
}
}
impl From<clickhouse::error::Error> for ClickHouseError {
fn from(err: clickhouse::error::Error) -> Self {
use clickhouse::error::Error as Ch;
let kind = match &err {
Ch::InvalidParams(_) => ErrorKind::InvalidParams,
Ch::Network(_) => ErrorKind::Network,
Ch::TimedOut => ErrorKind::TimedOut,
Ch::BadResponse(_) => ErrorKind::BadResponse,
Ch::RowNotFound => ErrorKind::NotFound,
Ch::SchemaMismatch(_) => ErrorKind::SchemaMismatch,
Ch::Compression(_)
| Ch::Decompression(_)
| Ch::InvalidUtf8Encoding(_)
| Ch::InvalidTagEncoding(_)
| Ch::VariantDiscriminatorIsOutOfBound(_)
| Ch::NotEnoughData
| Ch::SequenceMustHaveLength
| Ch::DeserializeAnyNotSupported
| Ch::InvalidColumnsHeader(_) => ErrorKind::Decode,
Ch::Unsupported(_) => ErrorKind::Unsupported,
Ch::Custom(_) => ErrorKind::Custom,
_ => ErrorKind::Unknown,
};
Self::Database {
kind,
detail: err.to_string(),
}
}
}
impl ClickHouseError {
#[must_use]
pub fn detail(&self) -> Option<&str> {
match self {
Self::Database { detail, .. } | Self::Migration { detail, .. } => Some(detail),
_ => None,
}
}
#[must_use]
pub const fn kind(&self) -> Option<ErrorKind> {
match self {
Self::Database { kind, .. } => Some(*kind),
_ => None,
}
}
#[must_use]
pub const fn is_retryable(&self) -> bool {
matches!(
self,
Self::Timeout { .. }
| Self::Database {
kind: ErrorKind::Network | ErrorKind::TimedOut,
..
}
)
}
#[must_use]
pub const fn status(&self) -> StatusCode {
match self {
Self::Timeout { .. }
| Self::Database {
kind: ErrorKind::TimedOut,
..
} => StatusCode::GATEWAY_TIMEOUT,
Self::Database {
kind: ErrorKind::Network,
..
} => StatusCode::SERVICE_UNAVAILABLE,
Self::Database {
kind: ErrorKind::BadResponse,
..
} => StatusCode::BAD_GATEWAY,
Self::Database {
kind: ErrorKind::NotFound,
..
} => StatusCode::NOT_FOUND,
Self::Database {
kind: ErrorKind::InvalidParams,
..
} => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
#[must_use]
pub fn into_autumn(self) -> AutumnError {
match self.status() {
StatusCode::NOT_FOUND => AutumnError::not_found(self),
StatusCode::BAD_REQUEST => AutumnError::bad_request(self),
StatusCode::SERVICE_UNAVAILABLE => AutumnError::service_unavailable(self),
StatusCode::GATEWAY_TIMEOUT => AutumnError::query_timeout(self.to_string()),
_ => AutumnError::internal_server_error(self),
}
}
}
pub trait ClickHouseResultExt<T> {
fn or_http(self) -> Result<T, AutumnError>;
}
impl<T> ClickHouseResultExt<T> for Result<T, ClickHouseError> {
fn or_http(self) -> Result<T, AutumnError> {
self.map_err(ClickHouseError::into_autumn)
}
}
#[cfg(test)]
mod tests;