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}
31
32impl std::fmt::Display for PgError {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            PgError::Postgres(e) => write!(f, "postgres error: {e}"),
36            PgError::TypeConversion(msg) => write!(f, "type conversion error: {msg}"),
37            PgError::Tls(msg) => write!(f, "TLS error: {msg}"),
38            PgError::ConstraintViolation { constraint, detail } => {
39                write!(f, "constraint violation on '{constraint}': {detail}")
40            }
41            PgError::Timeout(msg) => write!(f, "postgres timeout: {msg}"),
42            PgError::PoolExhausted(msg) => write!(f, "postgres pool exhausted: {msg}"),
43            PgError::Copy(msg) => write!(f, "postgres COPY error: {msg}"),
44            PgError::Notify(msg) => write!(f, "postgres NOTIFY error: {msg}"),
45            PgError::Connection(msg) => write!(f, "postgres connection error: {msg}"),
46        }
47    }
48}
49
50impl std::error::Error for PgError {
51    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
52        match self {
53            PgError::Postgres(e) => Some(e),
54            PgError::TypeConversion(_)
55            | PgError::Tls(_)
56            | PgError::ConstraintViolation { .. }
57            | PgError::Timeout(_)
58            | PgError::PoolExhausted(_)
59            | PgError::Copy(_)
60            | PgError::Notify(_)
61            | PgError::Connection(_) => None,
62        }
63    }
64}
65
66impl From<tokio_postgres::Error> for PgError {
67    fn from(e: tokio_postgres::Error) -> Self {
68        use tokio_postgres::error::SqlState;
69        if let Some(db_err) = e.as_db_error() {
70            let code = db_err.code();
71            if code == &SqlState::UNIQUE_VIOLATION
72                || code == &SqlState::FOREIGN_KEY_VIOLATION
73                || code == &SqlState::CHECK_VIOLATION
74            {
75                return PgError::ConstraintViolation {
76                    constraint: db_err.constraint().unwrap_or("unknown").to_string(),
77                    detail: db_err.detail().unwrap_or("").to_string(),
78                };
79            }
80        }
81        PgError::Postgres(e)
82    }
83}
84
85impl From<PgError> for oxisql_core::OxiSqlError {
86    fn from(e: PgError) -> Self {
87        match e {
88            PgError::ConstraintViolation {
89                ref constraint,
90                ref detail,
91            } => oxisql_core::OxiSqlError::ConstraintViolation(format!("'{constraint}': {detail}")),
92            PgError::Timeout(msg) => oxisql_core::OxiSqlError::Timeout(msg),
93            PgError::PoolExhausted(msg) => oxisql_core::OxiSqlError::ConnectionPool(msg),
94            PgError::Postgres(ref inner) if inner.code().is_some() => {
95                oxisql_core::OxiSqlError::Execution(e.to_string())
96            }
97            PgError::Postgres(_) => oxisql_core::OxiSqlError::Other(e.to_string()),
98            // OxiSqlError::TypeMismatch uses &'static str — we cannot put a heap
99            // string there without leaking.  Use Other, which accepts String.
100            PgError::TypeConversion(msg) => {
101                oxisql_core::OxiSqlError::Other(format!("type conversion error: {msg}"))
102            }
103            PgError::Tls(msg) => oxisql_core::OxiSqlError::Other(msg),
104            PgError::Copy(msg) => oxisql_core::OxiSqlError::Other(format!("COPY error: {msg}")),
105            PgError::Notify(msg) => oxisql_core::OxiSqlError::Other(format!("NOTIFY error: {msg}")),
106            PgError::Connection(msg) => {
107                oxisql_core::OxiSqlError::Other(format!("connection error: {msg}"))
108            }
109        }
110    }
111}