use std::io::ErrorKind;
use thiserror::Error;
pub const DISCONNECT_KINDS: &[ErrorKind] = &[ErrorKind::BrokenPipe, ErrorKind::ConnectionReset];
pub const CONNECTION_ERROR_KINDS: &[ErrorKind] = &[
ErrorKind::BrokenPipe,
ErrorKind::ConnectionReset,
ErrorKind::ConnectionAborted,
ErrorKind::UnexpectedEof,
];
#[inline]
#[must_use]
pub fn is_disconnect_kind(kind: ErrorKind) -> bool {
DISCONNECT_KINDS.contains(&kind)
}
#[inline]
#[must_use]
pub fn is_connection_error_kind(kind: ErrorKind) -> bool {
CONNECTION_ERROR_KINDS.contains(&kind)
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ConnectionError {
#[error("Authentication failed for backend '{backend}': {response}")]
AuthenticationFailed { backend: String, response: String },
#[error("Connection limit exceeded for backend '{backend}': {response}")]
ConnectionLimitExceeded { backend: String, response: String },
#[error("Invalid greeting from backend '{backend}': {greeting}")]
InvalidGreeting { backend: String, greeting: String },
#[error("Connection pool exhausted for backend '{backend}' (max size: {max_size})")]
PoolExhausted { backend: String, max_size: usize },
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("TLS handshake failed for backend '{backend}': {source}")]
TlsHandshake {
backend: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("No DNS addresses found for {address}")]
DnsNoAddresses { address: String },
#[error("Password required but not configured for backend '{backend}'")]
PasswordRequired { backend: String },
#[error("Unexpected auth response from backend '{backend}': {response}")]
UnexpectedAuthResponse { backend: String, response: String },
#[error("Compression required but not supported by backend '{backend}': {response}")]
CompressionRequired { backend: String, response: String },
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn test_authentication_failed_error() {
let err = ConnectionError::AuthenticationFailed {
backend: "news.example.com".to_string(),
response: "502 Authentication failed".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("news.example.com"));
assert!(msg.contains("502"));
}
#[test]
fn test_pool_exhausted_error() {
let err = ConnectionError::PoolExhausted {
backend: "backend1".to_string(),
max_size: 20,
};
let msg = err.to_string();
assert!(msg.contains("backend1"));
assert!(msg.contains("20"));
}
#[test]
fn test_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout");
let conn_err: ConnectionError = io_err.into();
assert!(matches!(conn_err, ConnectionError::IoError(_)));
}
#[test]
fn test_invalid_greeting_error() {
let err = ConnectionError::InvalidGreeting {
backend: "news.server.com".to_string(),
greeting: "500 Server error".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Invalid greeting"));
assert!(msg.contains("news.server.com"));
}
#[test]
fn io_error_disconnect_kinds_are_classified_correctly() {
let broken_pipe = ConnectionError::IoError(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"broken pipe",
));
assert!(
matches!(&broken_pipe, ConnectionError::IoError(e) if is_disconnect_kind(e.kind()))
);
let reset = ConnectionError::IoError(std::io::Error::new(
std::io::ErrorKind::ConnectionReset,
"reset",
));
assert!(matches!(&reset, ConnectionError::IoError(e) if is_disconnect_kind(e.kind())));
let other = ConnectionError::IoError(std::io::Error::other("other"));
assert!(!matches!(&other, ConnectionError::IoError(e) if is_disconnect_kind(e.kind())));
}
#[test]
fn test_tls_handshake_error() {
let err = ConnectionError::TlsHandshake {
backend: "secure.server.com".to_string(),
source: Box::new(std::io::Error::other("TLS handshake failed")),
};
let msg = err.to_string();
assert!(msg.contains("TLS handshake failed"));
assert!(msg.contains("secure.server.com"));
assert!(err.source().is_some());
}
#[test]
fn test_connection_limit_exceeded_error() {
let err = ConnectionError::ConnectionLimitExceeded {
backend: "news.example.com".to_string(),
response: "482 Connection limit exceeded".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("Connection limit exceeded"));
assert!(msg.contains("news.example.com"));
assert!(msg.contains("482"));
}
}