use std::io;
use armour_core::persist::PersistError;
#[derive(thiserror::Error, Debug)]
pub enum DbError {
#[error("client error: {0}")]
Client(&'static str),
#[error("key not found")]
KeyNotFound,
#[error("not implemented")]
NotImplemented,
#[error("storage error: {0}")]
Internal(&'static str),
#[error(transparent)]
Io(#[from] io::Error),
#[error("CRC32 mismatch: expected {expected:#x}, got {actual:#x}")]
CrcMismatch { expected: u32, actual: u32 },
#[error("corrupted entry at offset {offset}")]
CorruptedEntry { offset: u64 },
#[error("invalid configuration: {0}")]
Config(&'static str),
#[error("CAS value mismatch")]
CasMismatch,
#[error("key already exists")]
KeyExists,
#[error("stale DiskLoc - file removed by compaction")]
StaleDiskLoc,
#[error("all slots are occupied")]
SlotsFull,
#[error("key routes to a different shard")]
ShardMismatch,
#[error("disk format mismatch: {0}")]
FormatMismatch(String),
#[cfg(feature = "encryption")]
#[error("encryption error: {0}")]
EncryptionError(String),
#[cfg(feature = "replication")]
#[error("replication error: {0}")]
Replication(String),
#[cfg(feature = "replication")]
#[error("shard count mismatch: leader has {leader}, follower has {follower}")]
ShardCountMismatch { leader: usize, follower: usize },
#[error("schema mismatch on '{name}': {kind}")]
SchemaMismatch {
name: String,
kind: SchemaMismatchKind,
},
#[error("recovery thread panicked")]
RecoveryPanic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SchemaMismatchKind {
TypHash { stored: u64, expected: u64 },
Downgrade { stored: u16, requested: u16 },
MissingStep { from: u16 },
}
impl core::fmt::Display for SchemaMismatchKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::TypHash { stored, expected } => write!(
f,
"type hash drift: stored={stored:#x} expected={expected:#x}"
),
Self::Downgrade { stored, requested } => write!(
f,
"version downgrade: stored={stored} requested={requested}"
),
Self::MissingStep { from } => write!(f, "missing migration from version {from}"),
}
}
}
impl DbError {
pub fn status_code(&self) -> u16 {
match self {
DbError::Client(_) => 400,
DbError::KeyNotFound => 404,
DbError::NotImplemented => 501,
DbError::SchemaMismatch { .. } => 422,
DbError::KeyExists => 409,
DbError::CasMismatch => 412,
_ => 500,
}
}
}
pub type DbResult<T> = Result<T, DbError>;
impl From<PersistError> for DbError {
fn from(err: PersistError) -> Self {
match err {
PersistError::Io(e) => Self::Io(e),
PersistError::Json(e) => Self::FormatMismatch(e.to_string()),
}
}
}
#[cfg(test)]
mod schema_mismatch_tests {
use super::*;
#[test]
fn schema_mismatch_typ_hash_maps_to_422() {
let e = DbError::SchemaMismatch {
name: "users".into(),
kind: SchemaMismatchKind::TypHash {
stored: 0xDEAD,
expected: 0xBEEF,
},
};
assert_eq!(e.status_code(), 422);
}
#[test]
fn schema_mismatch_downgrade_maps_to_422() {
let e = DbError::SchemaMismatch {
name: "users".into(),
kind: SchemaMismatchKind::Downgrade {
stored: 3,
requested: 2,
},
};
assert_eq!(e.status_code(), 422);
}
#[test]
fn schema_mismatch_missing_step_maps_to_422() {
let e = DbError::SchemaMismatch {
name: "users".into(),
kind: SchemaMismatchKind::MissingStep { from: 1 },
};
assert_eq!(e.status_code(), 422);
}
#[test]
fn key_exists_maps_to_409() {
assert_eq!(DbError::KeyExists.status_code(), 409);
}
#[test]
fn cas_mismatch_maps_to_412() {
assert_eq!(DbError::CasMismatch.status_code(), 412);
}
}