Skip to main content

mnm_store/
error.rs

1//! Storage-layer error wrapping. sqlx errors are mapped to typed [`StoreError`]
2//! variants so callers don't need to match on sqlx internals.
3
4use thiserror::Error;
5
6/// All the ways a storage call can fail.
7#[derive(Debug, Error)]
8pub enum StoreError {
9    /// The row asked for does not exist.
10    #[error("row not found")]
11    NotFound,
12    /// A uniqueness constraint was violated (slug, revision, etc.).
13    #[error("unique constraint violated: {0}")]
14    UniqueViolation(String),
15    /// A check constraint or trigger rejected the operation.
16    #[error("constraint violated: {0}")]
17    CheckViolation(String),
18    /// Foreign-key constraint violated.
19    #[error("foreign-key violation: {0}")]
20    ForeignKeyViolation(String),
21    /// Any other database-side error.
22    #[error("database error: {0}")]
23    Database(String),
24    /// Serialization failure when round-tripping a JSONB column.
25    #[error("json serialization error: {0}")]
26    Json(String),
27    /// Migration runner failed.
28    #[error("migration error: {0}")]
29    Migration(String),
30}
31
32impl From<sqlx::Error> for StoreError {
33    fn from(err: sqlx::Error) -> Self {
34        match err {
35            sqlx::Error::RowNotFound => Self::NotFound,
36            sqlx::Error::Database(ref db_err) => {
37                let constraint = db_err.constraint().unwrap_or("");
38                let msg = db_err.message().to_string();
39                let code = db_err.code().map(|c| c.to_string()).unwrap_or_default();
40                // PostgreSQL SQLSTATE codes
41                match code.as_str() {
42                    "23505" => Self::UniqueViolation(format!("{constraint}: {msg}")),
43                    "23503" => Self::ForeignKeyViolation(format!("{constraint}: {msg}")),
44                    "23514" | "P0001" => Self::CheckViolation(format!("{constraint}: {msg}")),
45                    _ => Self::Database(format!("{code}: {msg}")),
46                }
47            }
48            sqlx::Error::Migrate(e) => Self::Migration(e.to_string()),
49            sqlx::Error::ColumnDecode { source, .. } | sqlx::Error::Decode(source) => {
50                Self::Json(source.to_string())
51            }
52            other => Self::Database(other.to_string()),
53        }
54    }
55}
56
57/// Crate-local Result alias.
58pub type Result<T> = std::result::Result<T, StoreError>;