use sea_orm::DbErr;
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum DbError {
#[error("Connection error: {0}")]
Connection(#[from] DbErr),
#[error("Configuration error: {0}")]
Config(String),
#[error("Permission denied: {0}")]
Permission(String),
#[error("Transaction error: {0}")]
Transaction(String),
#[error("Migration error: {0}")]
Migration(String),
}
impl DbError {
pub fn is_connection_error(&self) -> bool {
matches!(self, DbError::Connection(_))
}
pub fn is_query_error(&self) -> bool {
match self {
DbError::Connection(err) => {
let err_msg = err.to_string().to_lowercase();
!err_msg.contains("connection") && !err_msg.contains("timeout") && !err_msg.contains("refused")
}
_ => true,
}
}
pub fn is_transaction_error(&self) -> bool {
matches!(self, DbError::Transaction(_))
}
}
impl From<PoolError> for DbError {
fn from(err: PoolError) -> Self {
DbError::Config(err.to_string())
}
}
impl From<PermissionError> for DbError {
fn from(err: PermissionError) -> Self {
DbError::Permission(err.to_string())
}
}
impl From<ConfigError> for DbError {
fn from(err: ConfigError) -> Self {
DbError::Config(err.to_string())
}
}
impl From<MigrationError> for DbError {
fn from(err: MigrationError) -> Self {
DbError::Migration(err.to_string())
}
}
#[derive(Debug, thiserror::Error)]
pub enum PoolError {
#[error("Failed to acquire connection within timeout")]
AcquireTimeout,
#[error("Connection pool exhausted")]
PoolExhausted,
#[error("Failed to create connection: {0}")]
ConnectionFailed(String),
#[error("Health check failed: {0}")]
HealthCheckFailed(String),
}
#[derive(Debug, thiserror::Error)]
pub enum PermissionError {
#[error("Permission denied for {operation} on {resource}")]
Denied {
resource: String,
operation: String,
},
#[error("Role not found: {0}")]
RoleNotFound(String),
#[error("Invalid permission configuration: {0}")]
InvalidConfig(String),
#[error("Rate limit exceeded")]
RateLimited,
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("Missing required field: {0}")]
MissingField(&'static str),
#[error("Invalid format for field: {0}")]
InvalidFormat(String),
#[error("Configuration file not found")]
FileNotFound,
#[error("Failed to read configuration file: {0}")]
FileReadError(String),
#[error("Invalid database URL format: {0}")]
InvalidUrl(String),
#[error("Unsupported database protocol")]
UnsupportedProtocol,
#[error("Configuration file I/O error")]
IoError,
#[error("Environment variable error")]
EnvVarError,
#[error("Configuration validation failed")]
ValidationFailed,
#[cfg(feature = "dev")]
#[error(transparent)]
Internal(#[from] Box<dyn std::error::Error + Send + Sync>),
}
impl From<std::io::Error> for ConfigError {
fn from(_: std::io::Error) -> Self {
ConfigError::IoError
}
}
impl From<std::env::VarError> for ConfigError {
fn from(_: std::env::VarError) -> Self {
ConfigError::EnvVarError
}
}
#[derive(Debug, thiserror::Error)]
pub enum MigrationError {
#[error("Migration file not found: {0}")]
FileNotFound(String),
#[error("Failed to parse migration file: {0}")]
ParseError(String),
#[error("Migration execution failed: {0}")]
ExecutionError(String),
#[error("Migration version conflict: {0}")]
VersionConflict(String),
#[error("Migration rollback failed: {0}")]
RollbackError(String),
}
#[derive(Debug, thiserror::Error)]
pub enum AuditError {
#[error("Failed to write audit log: {0}")]
WriteError(String),
#[error("Failed to serialize audit data: {0}")]
SerializationError(String),
#[error("Invalid audit configuration: {0}")]
ConfigError(String),
}
pub type DbResult<T> = Result<T, DbError>;
pub type PoolResult<T> = Result<T, PoolError>;
pub type PermissionResult<T> = Result<T, PermissionError>;
pub type ConfigResult<T> = Result<T, ConfigError>;
pub type MigrationResult<T> = Result<T, MigrationError>;
pub type AuditResult<T> = Result<T, AuditError>;
impl From<PoolError> for DbErr {
fn from(err: PoolError) -> Self {
DbErr::Custom(err.to_string())
}
}
impl From<PermissionError> for DbErr {
fn from(err: PermissionError) -> Self {
DbErr::Custom(err.to_string())
}
}
impl From<ConfigError> for DbErr {
fn from(err: ConfigError) -> Self {
DbErr::Custom(err.to_string())
}
}
impl From<MigrationError> for DbErr {
fn from(err: MigrationError) -> Self {
DbErr::Custom(err.to_string())
}
}
impl From<AuditError> for DbErr {
fn from(err: AuditError) -> Self {
DbErr::Custom(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_db_error_is_connection_error() {
let conn_err = DbError::Connection(sea_orm::DbErr::ConnectionAcquire(sea_orm::ConnAcquireErr::Timeout));
assert!(conn_err.is_connection_error());
let config_err = DbError::Config("test".to_string());
assert!(!config_err.is_connection_error());
let perm_err = DbError::Permission("denied".to_string());
assert!(!perm_err.is_connection_error());
let txn_err = DbError::Transaction("rollback".to_string());
assert!(!txn_err.is_connection_error());
}
#[test]
fn test_db_error_is_transaction_error() {
let txn_err = DbError::Transaction("deadlock".to_string());
assert!(txn_err.is_transaction_error());
let conn_err = DbError::Connection(sea_orm::DbErr::ConnectionAcquire(sea_orm::ConnAcquireErr::Timeout));
assert!(!conn_err.is_transaction_error());
let config_err = DbError::Config("test".to_string());
assert!(!config_err.is_transaction_error());
}
#[test]
fn test_db_error_is_query_error() {
let conn_err = DbError::Connection(sea_orm::DbErr::Query(sea_orm::RuntimeErr::Internal(
"syntax error".to_string(),
)));
assert!(conn_err.is_query_error());
let conn_err2 = DbError::Connection(sea_orm::DbErr::ConnectionAcquire(sea_orm::ConnAcquireErr::Timeout));
assert!(!conn_err2.is_query_error());
let config_err = DbError::Config("test".to_string());
assert!(config_err.is_query_error());
let perm_err = DbError::Permission("denied".to_string());
assert!(perm_err.is_query_error());
}
#[test]
fn test_from_pool_error() {
let pool_err = PoolError::PoolExhausted;
let db_err: DbError = pool_err.into();
match db_err {
DbError::Config(msg) => {
assert!(msg.contains("pool") || msg.contains("exhausted"));
}
_ => panic!("Expected DbError::Config"),
}
}
#[test]
fn test_from_permission_error() {
let perm_err = PermissionError::Denied {
resource: "users".to_string(),
operation: "select".to_string(),
};
let db_err: DbError = perm_err.into();
match db_err {
DbError::Permission(msg) => {
assert!(msg.contains("users") || msg.contains("select"));
}
_ => panic!("Expected DbError::Permission"),
}
}
#[test]
fn test_from_config_error() {
let config_err = ConfigError::MissingField("url");
let db_err: DbError = config_err.into();
match db_err {
DbError::Config(msg) => {
assert!(msg.contains("url") || msg.contains("missing"));
}
_ => panic!("Expected DbError::Config"),
}
}
#[test]
fn test_from_migration_error() {
let mig_err = MigrationError::ExecutionError("failed".to_string());
let db_err: DbError = mig_err.into();
match db_err {
DbError::Migration(msg) => {
assert!(msg.contains("failed"));
}
_ => panic!("Expected DbError::Migration"),
}
}
#[test]
fn test_db_result_alias() {
let success: DbResult<i32> = Ok(42);
assert_eq!(success, Ok(42));
let failure: DbResult<i32> = Err(DbError::Config("error".to_string()));
assert!(failure.is_err());
}
}