signet_cold_sql/error.rs
1//! Error types for cold SQL storage.
2
3/// Postgres SQLSTATE for `query_canceled` — emitted by the server when
4/// `statement_timeout` trips a query.
5const PG_SQLSTATE_QUERY_CANCELED: &str = "57014";
6
7/// Errors that can occur in cold SQL storage operations.
8#[derive(Debug, thiserror::Error)]
9pub enum SqlColdError {
10 /// The query was cancelled by Postgres `statement_timeout`
11 /// (SQLSTATE 57014).
12 ///
13 /// The configured deadline (read or write timeout) is not threaded
14 /// through to this conversion — the variant is detected at the
15 /// `From<sqlx::Error>` boundary, which has no method context. The
16 /// handle surfaces this as
17 /// [`signet_cold::ColdStorageError::DeadlineExceeded`], which is
18 /// what callers and metrics should match on.
19 #[error("postgres statement_timeout exceeded")]
20 Timeout,
21
22 /// A sqlx database error occurred.
23 #[error("sqlx error: {0}")]
24 Sqlx(sqlx::Error),
25
26 /// A data conversion error occurred.
27 #[error("conversion error: {0}")]
28 Convert(String),
29}
30
31impl From<sqlx::Error> for SqlColdError {
32 fn from(e: sqlx::Error) -> Self {
33 if let sqlx::Error::Database(ref db) = e
34 && db.code().as_deref() == Some(PG_SQLSTATE_QUERY_CANCELED)
35 {
36 return Self::Timeout;
37 }
38 Self::Sqlx(e)
39 }
40}
41
42impl From<SqlColdError> for signet_cold::ColdStorageError {
43 fn from(error: SqlColdError) -> Self {
44 match error {
45 // Duration unknown at this conversion boundary; surface as
46 // ZERO so callers can match on `DeadlineExceeded(_)` and
47 // metrics see the deadline error class. Threading the
48 // configured deadline is a separate refactor.
49 SqlColdError::Timeout => Self::DeadlineExceeded(std::time::Duration::ZERO),
50 other => Self::Backend(Box::new(other)),
51 }
52 }
53}