1pub mod retry;
12pub mod server;
13pub mod sqlstate;
14
15use std::fmt;
16
17use crate::protocol::ProtocolError;
18use crate::types::Error as TypeConversionError;
19
20pub use crate::transport::TransportError;
21pub use server::PgServerError;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum PoolErrorVariant {
32 Exhausted,
33 Closed,
34 CreateFailed(String),
35 ResetFailed(String),
36}
37
38impl std::fmt::Display for PoolErrorVariant {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 PoolErrorVariant::Exhausted => write!(f, "connection pool exhausted"),
42 PoolErrorVariant::Closed => write!(f, "connection pool is closed"),
43 PoolErrorVariant::CreateFailed(msg) => {
44 write!(f, "failed to create pool connection: {msg}")
45 }
46 PoolErrorVariant::ResetFailed(msg) => write!(f, "connection reset failed: {msg}"),
47 }
48 }
49}
50
51impl std::error::Error for PoolErrorVariant {}
52
53#[derive(Debug)]
74#[non_exhaustive]
75pub enum PgError {
76 Server(Box<PgServerError>),
84
85 Protocol(ProtocolError),
87
88 Transport(TransportError),
90
91 Auth(String),
93
94 TypeConversion(TypeConversionError),
96
97 Config(String),
99
100 ConnectionClosed,
102
103 UnexpectedNull { column: String },
105
106 ColumnNotFound { name: String },
108
109 ColumnIndexOutOfBounds { index: usize, count: usize },
111
112 Timeout,
114
115 Pool(PoolErrorVariant),
117
118 InvalidState(String),
120
121 Unsupported(String),
123
124 Cancelled,
126
127 Io(std::io::Error),
129
130 #[cfg(feature = "tls")]
132 Tls(rustls::Error),
133
134 Other(String),
136}
137
138impl std::fmt::Display for PgError {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 PgError::Server(e) => write!(f, "server error: {}", e),
142 PgError::Protocol(e) => write!(f, "protocol error: {}", e),
143 PgError::Transport(e) => write!(f, "transport error: {}", e),
144 PgError::Auth(msg) => write!(f, "authentication error: {}", msg),
145 PgError::TypeConversion(e) => write!(f, "type conversion error: {}", e),
146 PgError::Config(msg) => write!(f, "configuration error: {}", msg),
147 PgError::ConnectionClosed => write!(f, "connection closed"),
148 PgError::UnexpectedNull { column } => write!(f, "unexpected NULL in column {}", column),
149 PgError::ColumnNotFound { name } => write!(f, "column not found: {}", name),
150 PgError::ColumnIndexOutOfBounds { index, count } => {
151 write!(
152 f,
153 "column index {} out of bounds (have {} columns)",
154 index, count
155 )
156 }
157 PgError::Timeout => write!(f, "operation timed out"),
158 PgError::Pool(e) => write!(f, "pool error: {e}"),
159 PgError::InvalidState(msg) => write!(f, "invalid connection state: {}", msg),
160 PgError::Unsupported(msg) => write!(f, "unsupported: {}", msg),
161 PgError::Cancelled => write!(f, "cancelled"),
162 PgError::Io(e) => write!(f, "I/O error: {}", e),
163 #[cfg(feature = "tls")]
164 PgError::Tls(e) => write!(f, "TLS error: {}", e),
165 PgError::Other(msg) => write!(f, "{}", msg),
166 }
167 }
168}
169
170impl std::error::Error for PgError {
171 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
172 match self {
173 PgError::Server(e) => Some(e.as_ref()),
174 PgError::Protocol(e) => Some(e),
175 PgError::Transport(e) => Some(e),
176 PgError::TypeConversion(e) => Some(e),
177 PgError::Io(e) => Some(e),
178 PgError::Pool(e) => Some(e),
179 #[cfg(feature = "tls")]
180 PgError::Tls(e) => Some(e),
181 _ => None,
182 }
183 }
184}
185
186impl From<PgServerError> for PgError {
191 fn from(e: PgServerError) -> Self {
192 PgError::Server(Box::new(e))
193 }
194}
195
196impl From<ProtocolError> for PgError {
197 fn from(e: ProtocolError) -> Self {
198 PgError::Protocol(e)
199 }
200}
201
202impl From<TransportError> for PgError {
203 fn from(e: TransportError) -> Self {
204 PgError::Transport(e)
205 }
206}
207
208impl From<TypeConversionError> for PgError {
209 fn from(e: TypeConversionError) -> Self {
210 PgError::TypeConversion(e)
211 }
212}
213
214impl From<std::io::Error> for PgError {
215 fn from(e: std::io::Error) -> Self {
216 PgError::Io(e)
217 }
218}
219
220#[cfg(feature = "tls")]
221impl From<rustls::Error> for PgError {
222 fn from(e: rustls::Error) -> Self {
223 PgError::Tls(e)
224 }
225}
226
227pub type Error = PgError;
229
230impl PgError {
235 pub fn is_connection_broken(&self) -> bool {
238 match self {
239 PgError::ConnectionClosed => true,
240 PgError::Transport(TransportError::ConnectionReset) => true,
241 PgError::Transport(TransportError::UnexpectedEof) => true,
242 PgError::Transport(TransportError::ConnectionRefused) => true,
243 PgError::Server(ref e) => {
244 e.is_connection_exception() || e.is_admin_shutdown() || e.is_crash_shutdown()
245 }
246 PgError::Io(ref e) => {
247 matches!(
248 e.kind(),
249 std::io::ErrorKind::ConnectionReset
250 | std::io::ErrorKind::ConnectionAborted
251 | std::io::ErrorKind::BrokenPipe
252 | std::io::ErrorKind::UnexpectedEof
253 )
254 }
255 _ => false,
256 }
257 }
258
259 pub fn is_retryable(&self) -> bool {
261 match self {
262 PgError::Server(e) => e.is_serialization_failure() || e.is_deadlock_detected(),
263 PgError::Transport(TransportError::Timeout) => true,
264 PgError::Timeout => true,
265 _ => false,
266 }
267 }
268
269 pub fn code(&self) -> Option<&str> {
271 match self {
272 PgError::Server(e) => Some(&e.code),
273 _ => None,
274 }
275 }
276
277 pub fn context(self, msg: impl Into<String>) -> Self {
279 PgError::Other(format!("{}: {}", msg.into(), self))
280 }
281}
282
283pub type Result<T> = std::result::Result<T, PgError>;
285
286impl From<crate::auth::AuthError> for PgError {
291 fn from(e: crate::auth::AuthError) -> Self {
292 match e {
293 crate::auth::AuthError::PasswordRequired => PgError::Auth("password required".into()),
294 crate::auth::AuthError::UnsupportedSaslMechanisms(mechs) => {
295 PgError::Auth(format!("unsupported SASL mechanisms: {mechs:?}"))
296 }
297 crate::auth::AuthError::Scram(msg) => PgError::Auth(format!("SCRAM error: {msg}")),
298 crate::auth::AuthError::InsecureAuthentication { method } => PgError::Auth(format!(
299 "refusing {method} authentication over an unencrypted transport"
300 )),
301 crate::auth::AuthError::ServerError(msg) => PgError::Other(msg),
302 crate::auth::AuthError::UnexpectedMessage => {
303 PgError::Auth("unexpected message during authentication".into())
304 }
305 crate::auth::AuthError::Protocol(p) => PgError::Protocol(p),
306 crate::auth::AuthError::Transport(t) => PgError::Transport(t),
307 crate::auth::AuthError::Io(i) => PgError::Io(i),
308 crate::auth::AuthError::Utf8(u) => PgError::Other(u.to_string()),
309 }
310 }
311}
312
313#[cfg(test)]
318mod tests {
319 use super::*;
320 use std::error::Error;
321
322 #[test]
323 fn test_pg_error_display() {
324 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
325 (b'S', "ERROR".to_string()),
326 (b'C', "23505".to_string()),
327 (b'M', "duplicate key".to_string()),
328 ])));
329 let display = err.to_string();
330 assert!(display.contains("server error"));
331 assert!(display.contains("duplicate key"));
332 assert!(display.contains("23505"));
333 }
334
335 #[test]
336 fn test_pg_error_is_connection_broken() {
337 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
338 (b'S', "FATAL".to_string()),
339 (b'C', "08006".to_string()),
340 (b'M', "connection failure".to_string()),
341 ])));
342 assert!(err.is_connection_broken());
343
344 assert!(PgError::Transport(TransportError::ConnectionReset).is_connection_broken());
345 assert!(PgError::Transport(TransportError::UnexpectedEof).is_connection_broken());
346 assert!(PgError::Transport(TransportError::ConnectionRefused).is_connection_broken());
347 assert!(!PgError::Transport(TransportError::Timeout).is_connection_broken());
348 assert!(PgError::ConnectionClosed.is_connection_broken());
349
350 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
351 (b'S', "FATAL".to_string()),
352 (b'C', "57P01".to_string()),
353 (b'M', "admin shutdown".to_string()),
354 ])));
355 assert!(err.is_connection_broken());
356
357 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
358 (b'S', "ERROR".to_string()),
359 (b'C', "23505".to_string()),
360 (b'M', "duplicate key".to_string()),
361 ])));
362 assert!(!err.is_connection_broken());
363 }
364
365 #[test]
366 fn test_pg_error_is_retryable() {
367 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
368 (b'S', "ERROR".to_string()),
369 (b'C', "40001".to_string()),
370 (b'M', "serialization failure".to_string()),
371 ])));
372 assert!(err.is_retryable());
373
374 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
375 (b'S', "ERROR".to_string()),
376 (b'C', "40P01".to_string()),
377 (b'M', "deadlock detected".to_string()),
378 ])));
379 assert!(err.is_retryable());
380
381 assert!(PgError::Transport(TransportError::Timeout).is_retryable());
382 assert!(PgError::Timeout.is_retryable());
383
384 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
385 (b'S', "ERROR".to_string()),
386 (b'C', "23505".to_string()),
387 (b'M', "duplicate key".to_string()),
388 ])));
389 assert!(!err.is_retryable());
390 assert!(!PgError::ConnectionClosed.is_retryable());
391 }
392
393 #[test]
394 fn test_pg_error_code() {
395 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
396 (b'S', "ERROR".to_string()),
397 (b'C', "23505".to_string()),
398 (b'M', "duplicate key".to_string()),
399 ])));
400 assert_eq!(err.code(), Some("23505"));
401 assert_eq!(PgError::ConnectionClosed.code(), None);
402 }
403
404 #[test]
405 fn test_pg_error_context() {
406 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
407 (b'S', "ERROR".to_string()),
408 (b'C', "23505".to_string()),
409 (b'M', "duplicate key".to_string()),
410 ])));
411 let with_ctx = err.context("inserting user");
412 match with_ctx {
413 PgError::Other(msg) => {
414 assert!(msg.contains("inserting user"));
415 assert!(msg.contains("duplicate key"));
416 }
417 _ => panic!("expected Other variant with context"),
418 }
419 }
420
421 #[test]
422 fn test_from_protocol_error() {
423 let err = PgError::from(ProtocolError::UnexpectedEof);
424 assert!(matches!(err, PgError::Protocol(_)));
425 }
426
427 #[test]
428 fn test_from_transport_error() {
429 let err = PgError::from(TransportError::ConnectionRefused);
430 assert!(matches!(err, PgError::Transport(_)));
431 }
432
433 #[test]
434 fn test_from_io_error() {
435 let err = PgError::from(std::io::Error::new(
436 std::io::ErrorKind::BrokenPipe,
437 "broken",
438 ));
439 assert!(matches!(err, PgError::Io(_)));
440 }
441
442 #[test]
443 fn test_io_error_is_connection_broken() {
444 let err = PgError::Io(std::io::Error::new(
445 std::io::ErrorKind::BrokenPipe,
446 "broken",
447 ));
448 assert!(err.is_connection_broken());
449
450 let err = PgError::Io(std::io::Error::new(
451 std::io::ErrorKind::ConnectionReset,
452 "reset",
453 ));
454 assert!(err.is_connection_broken());
455
456 let err = PgError::Io(std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout"));
457 assert!(!err.is_connection_broken());
458 }
459
460 #[test]
461 fn test_error_source_chain() {
462 let err = PgError::Server(Box::new(PgServerError::from_fields(vec![
463 (b'S', "ERROR".to_string()),
464 (b'C', "23505".to_string()),
465 (b'M', "duplicate key".to_string()),
466 ])));
467 assert!(err.source().is_some());
468
469 let err = PgError::Transport(TransportError::ConnectionReset);
470 assert!(err.source().is_some());
471
472 let err = PgError::ConnectionClosed;
473 assert!(err.source().is_none());
474 }
475
476 #[test]
477 fn test_unexpected_null_display() {
478 let err = PgError::UnexpectedNull {
479 column: "id".to_string(),
480 };
481 assert_eq!(err.to_string(), "unexpected NULL in column id");
482 }
483
484 #[test]
485 fn test_column_not_found_display() {
486 let err = PgError::ColumnNotFound {
487 name: "email".to_string(),
488 };
489 assert_eq!(err.to_string(), "column not found: email");
490 }
491
492 #[test]
493 fn test_column_index_out_of_bounds_display() {
494 let err = PgError::ColumnIndexOutOfBounds { index: 5, count: 3 };
495 assert!(err.to_string().contains("5"));
496 assert!(err.to_string().contains("3"));
497 }
498}