Skip to main content

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}