Skip to main content

modkit_db/secure/
error.rs

1use uuid::Uuid;
2
3/// Errors that can occur during scoped query execution.
4#[derive(thiserror::Error, Debug)]
5pub enum ScopeError {
6    /// Database error occurred during query execution.
7    #[error("database error: {0}")]
8    Db(#[from] sea_orm::DbErr),
9
10    /// Invalid scope configuration.
11    #[error("invalid scope: {0}")]
12    Invalid(&'static str),
13
14    /// Tenant isolation violation: `tenant_id` is not included in the current scope.
15    #[error("access denied: tenant_id not present in security scope ({tenant_id})")]
16    TenantNotInScope { tenant_id: Uuid },
17
18    /// Operation denied - entity not accessible in current security scope.
19    #[error("access denied: {0}")]
20    Denied(&'static str),
21}
22
23impl ScopeError {
24    /// Returns `true` if this error wraps a unique-constraint violation.
25    #[must_use]
26    pub fn is_unique_violation(&self) -> bool {
27        match self {
28            Self::Db(db_err) => is_unique_violation(db_err),
29            _ => false,
30        }
31    }
32}
33
34/// Check whether a `sea_orm::DbErr` represents a unique-constraint violation.
35///
36/// First tries `SeaORM`'s built-in `sql_err()` detection (SQLSTATE-based).
37/// Falls back to string matching on the error message for cases where
38/// `sql_err()` fails to classify the error (e.g. certain connection proxies
39/// or driver wrappers that strip the SQLSTATE code).
40///
41/// Recognized patterns across backends:
42/// - **Postgres** SQLSTATE `23505` — "`unique_violation`" / "duplicate key"
43/// - **`SQLite`** extended code `2067` — "UNIQUE constraint failed"
44/// - **`MySQL`** error `1062` — "Duplicate entry"
45#[must_use]
46pub fn is_unique_violation(err: &sea_orm::DbErr) -> bool {
47    // Fast path: SeaORM parsed the SQLSTATE / vendor code correctly.
48    if matches!(
49        err.sql_err(),
50        Some(sea_orm::SqlErr::UniqueConstraintViolation(_))
51    ) {
52        return true;
53    }
54
55    // Fallback: string-based detection for wrapped / proxied errors.
56    let msg = err.to_string().to_lowercase();
57    msg.contains("unique constraint")
58        || msg.contains("duplicate key")
59        || msg.contains("unique_violation")
60        || msg.contains("duplicate entry")
61        || msg.contains("unique constraint failed")
62}