Skip to main content

pool_mod/
error.rs

1//! The pool error type.
2
3use std::fmt;
4
5/// An error returned by a [`Pool`](crate::Pool) operation.
6///
7/// The type parameter `E` is the manager's own error type
8/// ([`Manager::Error`](crate::Manager::Error)). A failure that originates inside
9/// the manager — a connection that could not be opened, a transaction that could
10/// not be rolled back — is carried unchanged in [`Error::Backend`], so the caller
11/// can inspect or match on the underlying cause rather than a stringified copy of
12/// it.
13///
14/// The enum is `#[non_exhaustive]`: future versions may add variants, so a match
15/// on it must include a wildcard arm.
16///
17/// # Examples
18///
19/// Distinguish a saturated pool from a backend failure:
20///
21/// ```
22/// use pool_mod::Error;
23/// use std::convert::Infallible;
24///
25/// fn describe(err: &Error<Infallible>) -> &'static str {
26///     match err {
27///         Error::Timeout => "pool is busy, try again",
28///         Error::Closed => "pool is shut down",
29///         _ => "other",
30///     }
31/// }
32///
33/// assert_eq!(describe(&Error::Timeout), "pool is busy, try again");
34/// ```
35#[non_exhaustive]
36#[derive(Debug)]
37pub enum Error<E> {
38    /// The manager failed to create or recycle a resource. Carries the
39    /// manager's own error.
40    ///
41    /// Inspect the inner error to decide whether the failure is transient (worth
42    /// retrying) or permanent.
43    Backend(E),
44
45    /// The configured wait elapsed before a resource became available.
46    ///
47    /// The pool is healthy but saturated. Retry later, raise `max_size`, or hold
48    /// borrowed resources for less time.
49    Timeout,
50
51    /// The pool has been shut down with [`Pool::close`](crate::Pool::close).
52    ///
53    /// No further resources will be handed out; this is terminal for the pool.
54    Closed,
55
56    /// The pool could not be built because its configuration is invalid.
57    ///
58    /// The message names the constraint that was violated (for example, a
59    /// `max_size` of zero).
60    InvalidConfig(&'static str),
61}
62
63impl<E: fmt::Display> fmt::Display for Error<E> {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Error::Backend(source) => write!(f, "resource manager error: {source}"),
67            Error::Timeout => f.write_str("timed out waiting for a resource from the pool"),
68            Error::Closed => f.write_str("the pool has been closed"),
69            Error::InvalidConfig(reason) => write!(f, "invalid pool configuration: {reason}"),
70        }
71    }
72}
73
74impl<E> std::error::Error for Error<E>
75where
76    E: std::error::Error + 'static,
77{
78    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
79        match self {
80            Error::Backend(source) => Some(source),
81            Error::Timeout | Error::Closed | Error::InvalidConfig(_) => None,
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[derive(Debug)]
91    struct Backend;
92
93    impl fmt::Display for Backend {
94        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95            f.write_str("connection refused")
96        }
97    }
98
99    impl std::error::Error for Backend {}
100
101    #[test]
102    fn test_display_backend_includes_source_message() {
103        let err = Error::Backend(Backend);
104        assert_eq!(
105            err.to_string(),
106            "resource manager error: connection refused"
107        );
108    }
109
110    #[test]
111    fn test_display_timeout_is_actionable() {
112        let err: Error<Backend> = Error::Timeout;
113        assert_eq!(
114            err.to_string(),
115            "timed out waiting for a resource from the pool"
116        );
117    }
118
119    #[test]
120    fn test_display_invalid_config_names_constraint() {
121        let err: Error<Backend> = Error::InvalidConfig("max_size must be at least 1");
122        assert_eq!(
123            err.to_string(),
124            "invalid pool configuration: max_size must be at least 1"
125        );
126    }
127
128    #[test]
129    fn test_source_present_only_for_backend() {
130        use std::error::Error as _;
131
132        let backend = Error::Backend(Backend);
133        assert!(backend.source().is_some());
134
135        let timeout: Error<Backend> = Error::Timeout;
136        assert!(timeout.source().is_none());
137    }
138}