use std::fmt;
#[non_exhaustive]
#[derive(Debug)]
pub enum Error<E> {
Backend(E),
Timeout,
Closed,
InvalidConfig(&'static str),
}
impl<E: fmt::Display> fmt::Display for Error<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Backend(source) => write!(f, "resource manager error: {source}"),
Error::Timeout => f.write_str("timed out waiting for a resource from the pool"),
Error::Closed => f.write_str("the pool has been closed"),
Error::InvalidConfig(reason) => write!(f, "invalid pool configuration: {reason}"),
}
}
}
impl<E> std::error::Error for Error<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Backend(source) => Some(source),
Error::Timeout | Error::Closed | Error::InvalidConfig(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct Backend;
impl fmt::Display for Backend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("connection refused")
}
}
impl std::error::Error for Backend {}
#[test]
fn test_display_backend_includes_source_message() {
let err = Error::Backend(Backend);
assert_eq!(
err.to_string(),
"resource manager error: connection refused"
);
}
#[test]
fn test_display_timeout_is_actionable() {
let err: Error<Backend> = Error::Timeout;
assert_eq!(
err.to_string(),
"timed out waiting for a resource from the pool"
);
}
#[test]
fn test_display_invalid_config_names_constraint() {
let err: Error<Backend> = Error::InvalidConfig("max_size must be at least 1");
assert_eq!(
err.to_string(),
"invalid pool configuration: max_size must be at least 1"
);
}
#[test]
fn test_source_present_only_for_backend() {
use std::error::Error as _;
let backend = Error::Backend(Backend);
assert!(backend.source().is_some());
let timeout: Error<Backend> = Error::Timeout;
assert!(timeout.source().is_none());
}
}