use thiserror::Error;
#[derive(Error, Debug)]
pub enum ReplicationError {
#[error("Protocol parsing error: {0}")]
Protocol(String),
#[error("Buffer error: {0}")]
Buffer(String),
#[error("Transient connection error: {0}")]
TransientConnection(String),
#[error("Permanent connection error: {0}")]
PermanentConnection(String),
#[error("Replication connection error: {0}")]
ReplicationConnection(String),
#[error("Authentication failed: {0}")]
Authentication(String),
#[error("Replication slot error: {0}")]
ReplicationSlot(String),
#[error("Operation timed out: {0}")]
Timeout(String),
#[error("Operation was cancelled: {0}")]
Cancelled(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("String conversion error: {0}")]
StringConversion(#[from] std::ffi::NulError),
#[error("Replication error: {0}")]
Generic(String),
}
impl ReplicationError {
pub fn protocol<S: Into<String>>(msg: S) -> Self {
ReplicationError::Protocol(msg.into())
}
pub fn buffer<S: Into<String>>(msg: S) -> Self {
ReplicationError::Buffer(msg.into())
}
pub fn transient_connection<S: Into<String>>(msg: S) -> Self {
ReplicationError::TransientConnection(msg.into())
}
pub fn permanent_connection<S: Into<String>>(msg: S) -> Self {
ReplicationError::PermanentConnection(msg.into())
}
pub fn replication_connection<S: Into<String>>(msg: S) -> Self {
ReplicationError::ReplicationConnection(msg.into())
}
pub fn connection<S: Into<String>>(msg: S) -> Self {
ReplicationError::ReplicationConnection(msg.into())
}
pub fn authentication<S: Into<String>>(msg: S) -> Self {
ReplicationError::Authentication(msg.into())
}
pub fn replication_slot<S: Into<String>>(msg: S) -> Self {
ReplicationError::ReplicationSlot(msg.into())
}
pub fn timeout<S: Into<String>>(msg: S) -> Self {
ReplicationError::Timeout(msg.into())
}
pub fn cancelled<S: Into<String>>(msg: S) -> Self {
ReplicationError::Cancelled(msg.into())
}
pub fn config<S: Into<String>>(msg: S) -> Self {
ReplicationError::Config(msg.into())
}
pub fn generic<S: Into<String>>(msg: S) -> Self {
ReplicationError::Generic(msg.into())
}
pub fn is_transient(&self) -> bool {
matches!(
self,
ReplicationError::TransientConnection(_)
| ReplicationError::Timeout(_)
| ReplicationError::Io(_)
| ReplicationError::ReplicationConnection(_)
)
}
pub fn is_permanent(&self) -> bool {
matches!(
self,
ReplicationError::PermanentConnection(_)
| ReplicationError::Authentication(_)
| ReplicationError::ReplicationSlot(_)
)
}
pub fn is_cancelled(&self) -> bool {
matches!(self, ReplicationError::Cancelled(_))
}
}
pub type Result<T> = std::result::Result<T, ReplicationError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_protocol_error() {
let err = ReplicationError::protocol("test error");
assert_eq!(err.to_string(), "Protocol parsing error: test error");
match err {
ReplicationError::Protocol(msg) => assert_eq!(msg, "test error"),
_ => panic!("Expected Protocol error"),
}
}
#[test]
fn test_buffer_error() {
let err = ReplicationError::buffer("buffer overflow");
match err {
ReplicationError::Buffer(msg) => assert_eq!(msg, "buffer overflow"),
_ => panic!("Expected Buffer error"),
}
}
#[test]
fn test_transient_connection_error() {
let err = ReplicationError::transient_connection("connection lost");
assert!(err.is_transient());
assert!(!err.is_permanent());
assert!(!err.is_cancelled());
}
#[test]
fn test_permanent_connection_error() {
let err = ReplicationError::permanent_connection("invalid host");
assert!(!err.is_transient());
assert!(err.is_permanent());
}
#[test]
fn test_authentication_error() {
let err = ReplicationError::authentication("invalid password");
assert!(err.is_permanent());
assert_eq!(err.to_string(), "Authentication failed: invalid password");
}
#[test]
fn test_replication_slot_error() {
let err = ReplicationError::replication_slot("slot not found");
assert!(err.is_permanent());
}
#[test]
fn test_timeout_error() {
let err = ReplicationError::timeout("operation timed out");
assert!(err.is_transient());
}
#[test]
fn test_cancelled_error() {
let err = ReplicationError::cancelled("user cancelled");
assert!(err.is_cancelled());
assert!(!err.is_transient());
assert!(!err.is_permanent());
}
#[test]
fn test_config_error() {
let err = ReplicationError::config("invalid config");
assert!(!err.is_transient());
assert!(!err.is_permanent());
}
#[test]
fn test_generic_error() {
let err = ReplicationError::generic("something went wrong");
match err {
ReplicationError::Generic(msg) => assert_eq!(msg, "something went wrong"),
_ => panic!("Expected Generic error"),
}
}
#[test]
fn test_connection_alias() {
let err = ReplicationError::connection("test");
match err {
ReplicationError::ReplicationConnection(_) => {}
_ => panic!("Expected ReplicationConnection error"),
}
}
#[test]
fn test_connection_alias_display() {
let err = ReplicationError::connection("connection lost");
assert_eq!(
err.to_string(),
"Replication connection error: connection lost"
);
}
#[test]
fn test_replication_connection_display() {
let err = ReplicationError::replication_connection("slot error");
assert_eq!(err.to_string(), "Replication connection error: slot error");
assert!(err.is_transient());
assert!(!err.is_permanent());
}
#[test]
fn test_replication_slot_display() {
let err = ReplicationError::replication_slot("slot not found");
assert_eq!(err.to_string(), "Replication slot error: slot not found");
}
#[test]
fn test_cancelled_display() {
let err = ReplicationError::cancelled("user cancelled");
assert_eq!(err.to_string(), "Operation was cancelled: user cancelled");
}
#[test]
fn test_config_error_display() {
let err = ReplicationError::config("missing field");
assert_eq!(err.to_string(), "Configuration error: missing field");
assert!(!err.is_transient());
assert!(!err.is_permanent());
assert!(!err.is_cancelled());
}
#[test]
fn test_generic_error_display() {
let err = ReplicationError::generic("unknown issue");
assert_eq!(err.to_string(), "Replication error: unknown issue");
assert!(!err.is_transient());
assert!(!err.is_permanent());
assert!(!err.is_cancelled());
}
#[test]
fn test_io_error_conversion() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err: ReplicationError = io_err.into();
assert!(err.is_transient());
match err {
ReplicationError::Io(_) => {}
_ => panic!("Expected Io error"),
}
}
#[test]
fn test_nul_error_conversion() {
let nul_err = std::ffi::CString::new("hello\0world").unwrap_err();
let err: ReplicationError = nul_err.into();
match err {
ReplicationError::StringConversion(_) => {}
_ => panic!("Expected StringConversion error"),
}
}
#[test]
fn test_error_display() {
let err = ReplicationError::Protocol("test".to_string());
assert!(format!("{err}").contains("Protocol parsing error"));
let err = ReplicationError::Buffer("test".to_string());
assert!(format!("{err}").contains("Buffer error"));
let err = ReplicationError::Timeout("test".to_string());
assert!(format!("{err}").contains("Operation timed out"));
}
#[test]
fn test_result_type_alias() {
let ok_result: Result<i32> = Ok(42);
if let Ok(val) = ok_result {
assert_eq!(val, 42);
}
let err_result: Result<i32> = Err(ReplicationError::protocol("test error"));
assert!(err_result.is_err());
}
}