Skip to main content

oxisql_postgres/
error.rs

1/// Errors that can arise within the `oxisql-postgres` backend.
2///
3/// This is a backend-private type.  Public API surfaces translate these errors
4/// into [`oxisql_core::OxiSqlError`].
5#[derive(Debug)]
6pub enum PgError {
7    /// Error during connection or wire-protocol communication.
8    Postgres(tokio_postgres::Error),
9    /// A column value could not be mapped to the requested OxiSQL type.
10    TypeConversion(String),
11    /// TLS setup failed.
12    Tls(String),
13    /// A unique/foreign-key/check constraint was violated.
14    ConstraintViolation {
15        /// The PostgreSQL constraint name.
16        constraint: String,
17        /// Detail from the server error.
18        detail: String,
19    },
20    /// The operation timed out.
21    Timeout(String),
22    /// The connection pool is exhausted.
23    PoolExhausted(String),
24    /// A COPY IN or COPY OUT protocol error.
25    Copy(String),
26    /// A LISTEN/NOTIFY error (invalid channel name or unsupported connection type).
27    Notify(String),
28}
29
30impl std::fmt::Display for PgError {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            PgError::Postgres(e) => write!(f, "postgres error: {e}"),
34            PgError::TypeConversion(msg) => write!(f, "type conversion error: {msg}"),
35            PgError::Tls(msg) => write!(f, "TLS error: {msg}"),
36            PgError::ConstraintViolation { constraint, detail } => {
37                write!(f, "constraint violation on '{constraint}': {detail}")
38            }
39            PgError::Timeout(msg) => write!(f, "postgres timeout: {msg}"),
40            PgError::PoolExhausted(msg) => write!(f, "postgres pool exhausted: {msg}"),
41            PgError::Copy(msg) => write!(f, "postgres COPY error: {msg}"),
42            PgError::Notify(msg) => write!(f, "postgres NOTIFY error: {msg}"),
43        }
44    }
45}
46
47impl std::error::Error for PgError {
48    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
49        match self {
50            PgError::Postgres(e) => Some(e),
51            PgError::TypeConversion(_)
52            | PgError::Tls(_)
53            | PgError::ConstraintViolation { .. }
54            | PgError::Timeout(_)
55            | PgError::PoolExhausted(_)
56            | PgError::Copy(_)
57            | PgError::Notify(_) => None,
58        }
59    }
60}
61
62impl From<tokio_postgres::Error> for PgError {
63    fn from(e: tokio_postgres::Error) -> Self {
64        use tokio_postgres::error::SqlState;
65        if let Some(db_err) = e.as_db_error() {
66            let code = db_err.code();
67            if code == &SqlState::UNIQUE_VIOLATION
68                || code == &SqlState::FOREIGN_KEY_VIOLATION
69                || code == &SqlState::CHECK_VIOLATION
70            {
71                return PgError::ConstraintViolation {
72                    constraint: db_err.constraint().unwrap_or("unknown").to_string(),
73                    detail: db_err.detail().unwrap_or("").to_string(),
74                };
75            }
76        }
77        PgError::Postgres(e)
78    }
79}
80
81impl From<PgError> for oxisql_core::OxiSqlError {
82    fn from(e: PgError) -> Self {
83        match e {
84            PgError::ConstraintViolation {
85                ref constraint,
86                ref detail,
87            } => oxisql_core::OxiSqlError::ConstraintViolation(format!("'{constraint}': {detail}")),
88            PgError::Timeout(msg) => oxisql_core::OxiSqlError::Timeout(msg),
89            PgError::PoolExhausted(msg) => oxisql_core::OxiSqlError::ConnectionPool(msg),
90            PgError::Postgres(ref inner) if inner.code().is_some() => {
91                oxisql_core::OxiSqlError::Execution(e.to_string())
92            }
93            PgError::Postgres(_) => oxisql_core::OxiSqlError::Other(e.to_string()),
94            // OxiSqlError::TypeMismatch uses &'static str — we cannot put a heap
95            // string there without leaking.  Use Other, which accepts String.
96            PgError::TypeConversion(msg) => {
97                oxisql_core::OxiSqlError::Other(format!("type conversion error: {msg}"))
98            }
99            PgError::Tls(msg) => oxisql_core::OxiSqlError::Other(msg),
100            PgError::Copy(msg) => oxisql_core::OxiSqlError::Other(format!("COPY error: {msg}")),
101            PgError::Notify(msg) => oxisql_core::OxiSqlError::Other(format!("NOTIFY error: {msg}")),
102        }
103    }
104}