1use sqlx::postgres::PgQueryResult;
8use thiserror::Error;
9use ulid::Ulid;
10
11#[derive(Debug, Error)]
13#[error(transparent)]
14pub enum DatabaseError {
15 Driver {
17 #[from]
19 source: sqlx::Error,
20 },
21
22 Inconsistency(#[from] DatabaseInconsistencyError),
24
25 #[error("Invalid database operation")]
28 InvalidOperation {
29 #[source]
31 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
32 },
33
34 #[error("Expected {expected} rows to be affected, but {actual} rows were affected")]
37 RowsAffected {
38 expected: u64,
40
41 actual: u64,
43 },
44}
45
46impl DatabaseError {
47 pub(crate) fn ensure_affected_rows(
48 result: &PgQueryResult,
49 expected: u64,
50 ) -> Result<(), DatabaseError> {
51 let actual = result.rows_affected();
52 if actual == expected {
53 Ok(())
54 } else {
55 Err(DatabaseError::RowsAffected { expected, actual })
56 }
57 }
58
59 pub(crate) fn to_invalid_operation<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
60 Self::InvalidOperation {
61 source: Some(Box::new(e)),
62 }
63 }
64
65 pub(crate) const fn invalid_operation() -> Self {
66 Self::InvalidOperation { source: None }
67 }
68}
69
70#[derive(Debug, Error)]
72pub struct DatabaseInconsistencyError {
73 table: &'static str,
75
76 column: Option<&'static str>,
78
79 row: Option<Ulid>,
81
82 #[source]
84 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
85}
86
87impl std::fmt::Display for DatabaseInconsistencyError {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(f, "Database inconsistency on table {}", self.table)?;
90 if let Some(column) = self.column {
91 write!(f, " column {column}")?;
92 }
93 if let Some(row) = self.row {
94 write!(f, " row {row}")?;
95 }
96
97 Ok(())
98 }
99}
100
101impl DatabaseInconsistencyError {
102 #[must_use]
104 pub(crate) const fn on(table: &'static str) -> Self {
105 Self {
106 table,
107 column: None,
108 row: None,
109 source: None,
110 }
111 }
112
113 #[must_use]
115 pub(crate) const fn column(mut self, column: &'static str) -> Self {
116 self.column = Some(column);
117 self
118 }
119
120 #[must_use]
122 pub(crate) const fn row(mut self, row: Ulid) -> Self {
123 self.row = Some(row);
124 self
125 }
126
127 #[must_use]
129 pub(crate) fn source<E: std::error::Error + Send + Sync + 'static>(
130 mut self,
131 source: E,
132 ) -> Self {
133 self.source = Some(Box::new(source));
134 self
135 }
136}