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 RocksDbError {
#[error(transparent)]
Config(#[from] ConfigError),
#[error("RocksDB refused the call: {kind}")]
#[non_exhaustive]
Database {
kind: ErrorKind,
detail: String,
},
#[error("the column family `{name}` is not open: add it to `column_families`")]
#[non_exhaustive]
UnknownColumnFamily {
name: String,
},
#[error("the column family `{name}` is reserved for the plugin")]
#[non_exhaustive]
ReservedColumnFamily {
name: String,
},
#[error("the key has {size} bytes: the limit is {limit}")]
#[non_exhaustive]
KeyTooLarge {
size: usize,
limit: usize,
},
#[error("the value has {size} bytes: the limit is {limit}")]
#[non_exhaustive]
ValueTooLarge {
size: usize,
limit: usize,
},
#[error("the batch has {size} bytes: the limit is {limit}")]
#[non_exhaustive]
BatchTooLarge {
size: usize,
limit: usize,
},
#[error("the scan limit {limit} is not from 1 to {max}")]
#[non_exhaustive]
ScanLimit {
limit: usize,
max: usize,
},
#[error("one entry has more than {limit_bytes} bytes: read it with `get`")]
#[non_exhaustive]
EntryTooLarge {
limit_bytes: usize,
key: Vec<u8>,
},
#[error("{operation} is not supported for an in-memory database")]
#[non_exhaustive]
NotSupported {
operation: &'static str,
},
#[error("the database is read-only")]
ReadOnly,
#[error("the value does not {action} as JSON")]
#[non_exhaustive]
Json {
action: &'static str,
detail: String,
},
#[error("the call did not complete in {timeout:?}")]
#[non_exhaustive]
Timeout {
timeout: Duration,
},
#[error("the app shuts down: the RocksDB plugin starts no new calls")]
ShuttingDown,
#[error("the RocksDB task stopped before it gave a result")]
TaskFailed,
#[error("the RocksDB plugin is not installed: add `RocksDbPlugin` to the app")]
NotInstalled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
NotFound,
Corruption,
NotSupported,
InvalidArgument,
Io,
MergeInProgress,
Incomplete,
ShutdownInProgress,
TimedOut,
Aborted,
Busy,
Expired,
TryAgain,
CompactionTooLarge,
ColumnFamilyDropped,
Unknown,
}
impl From<rocksdb::ErrorKind> for ErrorKind {
fn from(kind: rocksdb::ErrorKind) -> Self {
use rocksdb::ErrorKind as Rocks;
match kind {
Rocks::NotFound => Self::NotFound,
Rocks::Corruption => Self::Corruption,
Rocks::NotSupported => Self::NotSupported,
Rocks::InvalidArgument => Self::InvalidArgument,
Rocks::IOError => Self::Io,
Rocks::MergeInProgress => Self::MergeInProgress,
Rocks::Incomplete => Self::Incomplete,
Rocks::ShutdownInProgress => Self::ShutdownInProgress,
Rocks::TimedOut => Self::TimedOut,
Rocks::Aborted => Self::Aborted,
Rocks::Busy => Self::Busy,
Rocks::Expired => Self::Expired,
Rocks::TryAgain => Self::TryAgain,
Rocks::CompactionTooLarge => Self::CompactionTooLarge,
Rocks::ColumnFamilyDropped => Self::ColumnFamilyDropped,
Rocks::Unknown => Self::Unknown,
}
}
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::NotFound => "not found",
Self::Corruption => "corruption",
Self::NotSupported => "not supported",
Self::InvalidArgument => "invalid argument",
Self::Io => "I/O error",
Self::MergeInProgress => "merge in progress",
Self::Incomplete => "incomplete",
Self::ShutdownInProgress => "shutdown in progress",
Self::TimedOut => "timed out",
Self::Aborted => "aborted",
Self::Busy => "busy",
Self::Expired => "expired",
Self::TryAgain => "try again",
Self::CompactionTooLarge => "compaction too large",
Self::ColumnFamilyDropped => "column family dropped",
Self::Unknown => "unknown error",
})
}
}
impl std::fmt::Debug for RocksDbError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("RocksDbError")
.field(&format_args!("{self}"))
.finish()
}
}
impl From<rocksdb::Error> for RocksDbError {
fn from(err: rocksdb::Error) -> Self {
Self::Database {
kind: ErrorKind::from(err.kind()),
detail: err.into_string(),
}
}
}
impl RocksDbError {
pub(crate) fn json(action: &'static str, err: &serde_json::Error) -> Self {
Self::Json {
action,
detail: err.to_string(),
}
}
#[must_use]
pub fn detail(&self) -> Option<&str> {
match self {
Self::Database { detail, .. } | Self::Json { 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::Busy | ErrorKind::TryAgain | ErrorKind::TimedOut,
..
}
)
}
#[must_use]
pub const fn status(&self) -> StatusCode {
match self {
Self::Timeout { .. } => StatusCode::GATEWAY_TIMEOUT,
Self::ShuttingDown
| Self::Database {
kind:
ErrorKind::Busy
| ErrorKind::TryAgain
| ErrorKind::TimedOut
| ErrorKind::ShutdownInProgress,
..
} => StatusCode::SERVICE_UNAVAILABLE,
Self::KeyTooLarge { .. } | Self::ValueTooLarge { .. } | Self::BatchTooLarge { .. } => {
StatusCode::PAYLOAD_TOO_LARGE
}
Self::ScanLimit { .. } => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
#[must_use]
pub fn into_autumn(self) -> AutumnError {
let status = self.status();
AutumnError::internal_server_error(self).with_status(status)
}
}
pub trait RocksDbResultExt<T> {
fn or_http(self) -> Result<T, AutumnError>;
}
impl<T> RocksDbResultExt<T> for Result<T, RocksDbError> {
fn or_http(self) -> Result<T, AutumnError> {
self.map_err(RocksDbError::into_autumn)
}
}
#[cfg(test)]
mod tests;