Skip to main content

arcature_data/
error.rs

1//! The typed [`DataError`] for the high-level data layer (AGENTS.md §18).
2//!
3//! Errors are typed and contextual — no raw `String` errors. A database error
4//! from SeaORM preserves the upstream [`sea_orm::DbErr`] (which itself carries
5//! context — connection, query, constraint, etc.); a raw SQLx error preserves
6//! [`sqlx::Error`]. Invalid pagination arguments are a distinct, typed variant
7//! so a caller can branch on the failure mode without string matching.
8
9use std::fmt;
10
11use arcature_db::sea_orm;
12use arcature_db::sqlx;
13
14/// The error returned by the high-level data layer.
15///
16/// One database-error variant preserves the upstream SeaORM error (SeaORM's
17/// `DbErr` is already contextual — it distinguishes connection, query, record
18/// not found, and constraint failures). A separate variant preserves raw
19/// SQLx errors for the escape-hatch path. Pagination validation failures are
20/// a distinct typed enum so they never collide with a database failure.
21#[derive(Debug)]
22pub enum DataError {
23    /// A SeaORM query, mutation, or transaction returned a database error.
24    Database(sea_orm::DbErr),
25    /// A raw SQLx operation (the escape hatch) returned a database error.
26    Sqlx(sqlx::Error),
27    /// A pagination argument was invalid (zero is not a valid page or
28    /// per-page size).
29    Pagination(PaginationError),
30}
31
32/// An invalid pagination argument (AGENTS.md §18: typed, not a string).
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum PaginationError {
35    /// The requested page number was zero; pages are 1-based.
36    PageMustBePositive,
37    /// The requested per-page size was zero; it must be at least 1.
38    PerPageMustBePositive,
39}
40
41impl DataError {
42    /// The stable, machine-readable error identifier (for structured logging
43    /// and `arc doctor`-style diagnostics). Stable strings, never the dynamic
44    /// message — callers may match on these.
45    #[must_use]
46    pub fn id(&self) -> &'static str {
47        match self {
48            Self::Database(_) => "arcature_data.database",
49            Self::Sqlx(_) => "arcature_data.sqlx",
50            Self::Pagination(PaginationError::PageMustBePositive) => {
51                "arcature_data.pagination.page_must_be_positive"
52            }
53            Self::Pagination(PaginationError::PerPageMustBePositive) => {
54                "arcature_data.pagination.per_page_must_be_positive"
55            }
56        }
57    }
58}
59
60impl fmt::Display for DataError {
61    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match self {
63            Self::Database(error) => write!(formatter, "database error: {error}"),
64            Self::Sqlx(error) => write!(formatter, "sqlx error: {error}"),
65            Self::Pagination(PaginationError::PageMustBePositive) => {
66                write!(formatter, "pagination page must be >= 1 (1-based)")
67            }
68            Self::Pagination(PaginationError::PerPageMustBePositive) => {
69                write!(formatter, "pagination per_page must be >= 1")
70            }
71        }
72    }
73}
74
75impl std::error::Error for DataError {
76    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
77        match self {
78            Self::Database(error) => Some(error),
79            Self::Sqlx(error) => Some(error),
80            Self::Pagination(_) => None,
81        }
82    }
83}
84
85impl From<sea_orm::DbErr> for DataError {
86    fn from(value: sea_orm::DbErr) -> Self {
87        Self::Database(value)
88    }
89}
90
91impl From<sqlx::Error> for DataError {
92    fn from(value: sqlx::Error) -> Self {
93        Self::Sqlx(value)
94    }
95}
96
97impl From<PaginationError> for DataError {
98    fn from(value: PaginationError) -> Self {
99        Self::Pagination(value)
100    }
101}