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    /// A connection-level error (e.g. cancel request failed).
29    Connection(String),
30    /// A replication-protocol error (slot management, negotiation, or unexpected server state).
31    Replication(String),
32    /// A malformed or truncated wire-protocol message was received (decoder bounds-check failure).
33    Protocol(String),
34}
35
36impl std::fmt::Display for PgError {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            PgError::Postgres(e) => write!(f, "postgres error: {e}"),
40            PgError::TypeConversion(msg) => write!(f, "type conversion error: {msg}"),
41            PgError::Tls(msg) => write!(f, "TLS error: {msg}"),
42            PgError::ConstraintViolation { constraint, detail } => {
43                write!(f, "constraint violation on '{constraint}': {detail}")
44            }
45            PgError::Timeout(msg) => write!(f, "postgres timeout: {msg}"),
46            PgError::PoolExhausted(msg) => write!(f, "postgres pool exhausted: {msg}"),
47            PgError::Copy(msg) => write!(f, "postgres COPY error: {msg}"),
48            PgError::Notify(msg) => write!(f, "postgres NOTIFY error: {msg}"),
49            PgError::Connection(msg) => write!(f, "postgres connection error: {msg}"),
50            PgError::Replication(msg) => write!(f, "postgres replication error: {msg}"),
51            PgError::Protocol(msg) => write!(f, "postgres protocol error: {msg}"),
52        }
53    }
54}
55
56impl std::error::Error for PgError {
57    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
58        match self {
59            PgError::Postgres(e) => Some(e),
60            PgError::TypeConversion(_)
61            | PgError::Tls(_)
62            | PgError::ConstraintViolation { .. }
63            | PgError::Timeout(_)
64            | PgError::PoolExhausted(_)
65            | PgError::Copy(_)
66            | PgError::Notify(_)
67            | PgError::Connection(_)
68            | PgError::Replication(_)
69            | PgError::Protocol(_) => None,
70        }
71    }
72}
73
74impl From<tokio_postgres::Error> for PgError {
75    fn from(e: tokio_postgres::Error) -> Self {
76        use tokio_postgres::error::SqlState;
77        if let Some(db_err) = e.as_db_error() {
78            let code = db_err.code();
79            if code == &SqlState::UNIQUE_VIOLATION
80                || code == &SqlState::FOREIGN_KEY_VIOLATION
81                || code == &SqlState::CHECK_VIOLATION
82            {
83                return PgError::ConstraintViolation {
84                    constraint: db_err.constraint().unwrap_or("unknown").to_string(),
85                    detail: db_err.detail().unwrap_or("").to_string(),
86                };
87            }
88        }
89        PgError::Postgres(e)
90    }
91}
92
93impl From<std::io::Error> for PgError {
94    fn from(e: std::io::Error) -> Self {
95        PgError::Connection(e.to_string())
96    }
97}
98
99impl From<PgError> for oxisql_core::OxiSqlError {
100    fn from(e: PgError) -> Self {
101        match e {
102            PgError::ConstraintViolation {
103                ref constraint,
104                ref detail,
105            } => oxisql_core::OxiSqlError::ConstraintViolation(format!("'{constraint}': {detail}")),
106            PgError::Timeout(msg) => oxisql_core::OxiSqlError::Timeout(msg),
107            PgError::PoolExhausted(msg) => oxisql_core::OxiSqlError::ConnectionPool(msg),
108            PgError::Postgres(ref inner) if inner.code().is_some() => {
109                oxisql_core::OxiSqlError::Execution(e.to_string())
110            }
111            PgError::Postgres(_) => oxisql_core::OxiSqlError::Other(e.to_string()),
112            // OxiSqlError::TypeMismatch uses &'static str — we cannot put a heap
113            // string there without leaking.  Use Other, which accepts String.
114            PgError::TypeConversion(msg) => {
115                oxisql_core::OxiSqlError::Other(format!("type conversion error: {msg}"))
116            }
117            PgError::Tls(msg) => oxisql_core::OxiSqlError::Other(msg),
118            PgError::Copy(msg) => oxisql_core::OxiSqlError::Other(format!("COPY error: {msg}")),
119            PgError::Notify(msg) => oxisql_core::OxiSqlError::Other(format!("NOTIFY error: {msg}")),
120            PgError::Connection(msg) => {
121                oxisql_core::OxiSqlError::Other(format!("connection error: {msg}"))
122            }
123            PgError::Replication(msg) => {
124                oxisql_core::OxiSqlError::Other(format!("replication error: {msg}"))
125            }
126            PgError::Protocol(msg) => {
127                oxisql_core::OxiSqlError::Other(format!("protocol error: {msg}"))
128            }
129        }
130    }
131}