1use std::fmt;
7
8#[derive(Debug)]
10pub enum ConnectionError {
11 TcpConnect {
13 host: String,
14 port: u16,
15 source: std::io::Error,
16 },
17
18 DnsResolution {
20 address: String,
21 source: std::io::Error,
22 },
23
24 SocketConfig {
26 operation: String,
27 source: std::io::Error,
28 },
29
30 AuthenticationFailed { backend: String, response: String },
32
33 InvalidGreeting { backend: String, greeting: String },
35
36 PoolExhausted { backend: String, max_size: usize },
38
39 StaleConnection { backend: String, reason: String },
41
42 IoError(std::io::Error),
44
45 TlsHandshake {
47 backend: String,
48 source: Box<dyn std::error::Error + Send + Sync>,
49 },
50
51 CertificateVerification { backend: String, reason: String },
53}
54
55impl fmt::Display for ConnectionError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::TcpConnect { host, port, source } => {
59 write!(f, "Failed to connect to {}:{}: {}", host, port, source)
60 }
61 Self::DnsResolution { address, source } => {
62 write!(f, "Failed to resolve DNS for {}: {}", address, source)
63 }
64 Self::SocketConfig { operation, source } => {
65 write!(f, "Failed to configure socket ({}): {}", operation, source)
66 }
67 Self::AuthenticationFailed { backend, response } => {
68 write!(
69 f,
70 "Authentication failed for backend '{}': {}",
71 backend, response
72 )
73 }
74 Self::InvalidGreeting { backend, greeting } => {
75 write!(
76 f,
77 "Invalid greeting from backend '{}': {}",
78 backend, greeting
79 )
80 }
81 Self::PoolExhausted { backend, max_size } => {
82 write!(
83 f,
84 "Connection pool exhausted for backend '{}' (max size: {})",
85 backend, max_size
86 )
87 }
88 Self::StaleConnection { backend, reason } => {
89 write!(f, "Stale connection to backend '{}': {}", backend, reason)
90 }
91 Self::IoError(e) => write!(f, "I/O error: {}", e),
92 Self::TlsHandshake { backend, source } => {
93 write!(
94 f,
95 "TLS handshake failed for backend '{}': {}",
96 backend, source
97 )
98 }
99 Self::CertificateVerification { backend, reason } => {
100 write!(
101 f,
102 "Certificate verification failed for backend '{}': {}",
103 backend, reason
104 )
105 }
106 }
107 }
108}
109
110impl std::error::Error for ConnectionError {
111 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
112 match self {
113 Self::TcpConnect { source, .. } => Some(source),
114 Self::DnsResolution { source, .. } => Some(source),
115 Self::SocketConfig { source, .. } => Some(source),
116 Self::IoError(e) => Some(e),
117 Self::TlsHandshake { source, .. } => Some(source.as_ref()),
118 _ => None,
119 }
120 }
121}
122
123impl From<std::io::Error> for ConnectionError {
124 fn from(err: std::io::Error) -> Self {
125 Self::IoError(err)
126 }
127}
128
129#[cfg(test)]
133mod tests {
134 use super::*;
135 use std::error::Error;
136
137 #[test]
138 fn test_tcp_connect_error() {
139 let err = ConnectionError::TcpConnect {
140 host: "example.com".to_string(),
141 port: 119,
142 source: std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused"),
143 };
144
145 let msg = err.to_string();
146 assert!(msg.contains("example.com"));
147 assert!(msg.contains("119"));
148 assert!(msg.contains("refused"));
149 }
150
151 #[test]
152 fn test_authentication_failed_error() {
153 let err = ConnectionError::AuthenticationFailed {
154 backend: "news.example.com".to_string(),
155 response: "502 Authentication failed".to_string(),
156 };
157
158 let msg = err.to_string();
159 assert!(msg.contains("news.example.com"));
160 assert!(msg.contains("502"));
161 }
162
163 #[test]
164 fn test_pool_exhausted_error() {
165 let err = ConnectionError::PoolExhausted {
166 backend: "backend1".to_string(),
167 max_size: 20,
168 };
169
170 let msg = err.to_string();
171 assert!(msg.contains("backend1"));
172 assert!(msg.contains("20"));
173 }
174
175 #[test]
176 fn test_from_io_error() {
177 let io_err = std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout");
178 let conn_err: ConnectionError = io_err.into();
179
180 assert!(matches!(conn_err, ConnectionError::IoError(_)));
181 }
182
183 #[test]
184 fn test_error_source() {
185 let io_err = std::io::Error::new(std::io::ErrorKind::ConnectionReset, "reset");
186 let err = ConnectionError::TcpConnect {
187 host: "test.com".to_string(),
188 port: 119,
189 source: io_err,
190 };
191
192 assert!(err.source().is_some());
193 }
194
195 #[test]
196 fn test_invalid_greeting_error() {
197 let err = ConnectionError::InvalidGreeting {
198 backend: "news.server.com".to_string(),
199 greeting: "500 Server error".to_string(),
200 };
201
202 let msg = err.to_string();
203 assert!(msg.contains("Invalid greeting"));
204 assert!(msg.contains("news.server.com"));
205 }
206
207 #[test]
208 fn test_stale_connection_error() {
209 let err = ConnectionError::StaleConnection {
210 backend: "backend2".to_string(),
211 reason: "Connection closed by peer".to_string(),
212 };
213
214 let msg = err.to_string();
215 assert!(msg.contains("Stale"));
216 assert!(msg.contains("backend2"));
217 }
218}