1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! Error types for the typed query layer.
/// Top-level error type returned by the typed query layer.
///
/// Most call sites bubble this up via `?`. Match on it to distinguish wire
/// errors (server reported a problem) from decode errors (server response
/// could not be mapped into the requested Rust type) from operational errors
/// (timeouts, pool exhaustion, I/O).
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum TypedError {
/// The underlying `pg-wired` connection returned a protocol-level error.
#[error("wire error: {0}")]
Wire(#[from] Box<pg_wired::PgWireError>),
/// A row's column value could not be decoded into the requested Rust type.
#[error("decode error: column {column}: {message}")]
Decode {
/// Zero-based column index in the row.
column: usize,
/// Decoder-specific failure message.
message: String,
},
/// `Row::get` was called with a column name that does not exist in the row.
#[error("column not found: {0}")]
ColumnNotFound(String),
/// A non-`Option` decode hit a SQL `NULL` in the named column index.
#[error("unexpected null in column {0}")]
UnexpectedNull(usize),
/// `fetch_one` (or similar) expected exactly one row but received a
/// different count.
#[error("row count mismatch: expected 1, got {0}")]
NotExactlyOne(usize),
/// The decoded column's type OID did not match the type the decoder expects.
#[error("type mismatch: expected OID {expected}, got {actual}")]
TypeMismatch {
/// OID the decoder was registered for.
expected: u32,
/// OID the server actually sent.
actual: u32,
},
/// The connection pool returned an error (e.g., timeout waiting for a
/// connection, exhausted retries on `connect`).
#[error("pool error: {0}")]
Pool(#[from] Box<pg_pool::PoolError<pg_wired::PgWireError>>),
/// The configured query timeout elapsed before the server responded.
#[error("query timed out after {0:?}")]
Timeout(std::time::Duration),
/// A `:name` parameter referenced in SQL was not supplied to
/// `query_named` / `execute_named`.
#[error("missing named parameter: :{0}")]
MissingParam(String),
/// Local I/O error (rare; usually wrapped into `Wire`).
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
/// Catch-all for misconfiguration (invalid connection string, missing
/// type registration, etc.).
#[error("{0}")]
Config(String),
/// A wrapper added by `with_sql` that pairs an inner error with the
/// (possibly truncated) SQL that produced it. Useful for log output.
#[error("query failed: {source} [SQL: {sql}]")]
QueryFailed {
/// The original error.
source: Box<TypedError>,
/// The SQL that produced the error (truncated to 200 bytes).
sql: String,
},
}
impl From<pg_wired::PgWireError> for TypedError {
fn from(e: pg_wired::PgWireError) -> Self {
Self::Wire(Box::new(e))
}
}
impl From<pg_pool::PoolError<pg_wired::PgWireError>> for TypedError {
fn from(e: pg_pool::PoolError<pg_wired::PgWireError>) -> Self {
Self::Pool(Box::new(e))
}
}
impl TypedError {
/// Attach SQL context to an error for debugging.
pub fn with_sql(self, sql: &str) -> Self {
let truncated = if sql.len() > 200 {
format!("{}...", &sql[..200])
} else {
sql.to_string()
};
TypedError::QueryFailed {
source: Box::new(self),
sql: truncated,
}
}
}