sea_orm/error.rs
1#[cfg(feature = "sqlx-dep")]
2pub use sqlx::error::Error as SqlxError;
3
4#[cfg(feature = "sqlx-mysql")]
5pub use sqlx::mysql::MySqlDatabaseError as SqlxMySqlError;
6
7#[cfg(feature = "sqlx-postgres")]
8pub use sqlx::postgres::PgDatabaseError as SqlxPostgresError;
9
10#[cfg(feature = "sqlx-sqlite")]
11pub use sqlx::sqlite::SqliteError as SqlxSqliteError;
12
13use std::sync::Arc;
14use thiserror::Error;
15
16/// An error from unsuccessful database operations
17#[derive(Error, Debug, Clone)]
18#[non_exhaustive]
19pub enum DbErr {
20 /// This error can happen when the connection pool is fully-utilized
21 #[error("Failed to acquire connection from pool: {0}")]
22 ConnectionAcquire(#[source] ConnAcquireErr),
23 /// Runtime type conversion error
24 #[error("Error converting `{from}` into `{into}`: {source}")]
25 TryIntoErr {
26 /// From type
27 from: &'static str,
28 /// Into type
29 into: &'static str,
30 /// TryError
31 source: Arc<dyn std::error::Error + Send + Sync>,
32 },
33 /// There was a problem with the database connection
34 #[error("Connection Error: {0}")]
35 Conn(#[source] RuntimeErr),
36 /// An operation did not execute successfully
37 #[error("Execution Error: {0}")]
38 Exec(#[source] RuntimeErr),
39 /// An error occurred while performing a query
40 #[error("Query Error: {0}")]
41 Query(#[source] RuntimeErr),
42 /// Type error: the specified type cannot be converted from u64. This is not a runtime error.
43 #[error("Type '{0}' cannot be converted from u64")]
44 ConvertFromU64(&'static str),
45 /// After an insert statement it was impossible to retrieve the last_insert_id
46 #[error("Failed to unpack last_insert_id")]
47 UnpackInsertId,
48 /// When updating, a model should know its primary key to check
49 /// if the record has been correctly updated, otherwise this error will occur
50 #[error("Failed to get primary key from model")]
51 UpdateGetPrimaryKey,
52 /// The record was not found in the database
53 #[error("RecordNotFound Error: {0}")]
54 RecordNotFound(String),
55 /// Thrown by `TryFrom<ActiveModel>`, which assumes all attributes are set/unchanged
56 #[error("Attribute {0} is NotSet")]
57 AttrNotSet(String),
58 /// A custom error
59 #[error("Custom Error: {0}")]
60 Custom(String),
61 /// Error occurred while parsing value as target type
62 #[error("Type Error: {0}")]
63 Type(String),
64 /// Error occurred while parsing json value as target type
65 #[error("Json Error: {0}")]
66 Json(String),
67 /// A migration error
68 #[error("Migration Error: {0}")]
69 Migration(String),
70 /// None of the records are inserted,
71 /// that probably means all of them conflict with existing records in the table
72 #[error("None of the records are inserted")]
73 RecordNotInserted,
74 /// None of the records are updated, that means a WHERE condition has no matches.
75 /// May be the table is empty or the record does not exist
76 #[error("None of the records are updated")]
77 RecordNotUpdated,
78 /// This operation is not supported by the database backend
79 #[error("Operation not supported by backend {db}: {ctx}")]
80 BackendNotSupported {
81 /// Database backend
82 db: &'static str,
83 /// Context
84 ctx: &'static str,
85 },
86 /// (Primary) Key arity mismatch
87 #[error("Key arity mismatch: expected {expected}, received {received}")]
88 KeyArityMismatch {
89 /// Expected value
90 expected: u8,
91 /// Received value
92 received: u8,
93 },
94 /// Primay key not set for update / delete
95 #[error("Primay key not set for {ctx}")]
96 PrimaryKeyNotSet {
97 /// Context
98 ctx: &'static str,
99 },
100 /// Error while running RBAC checks
101 #[error("RBAC error: {0}")]
102 RbacError(String),
103 /// Access denied after running RBAC checks
104 #[error("Access denied: cannot perform `{permission}` on `{resource}`")]
105 AccessDenied {
106 /// The required permission
107 permission: String,
108 /// The requested resource
109 resource: String,
110 },
111 /// Mutex was poisoned by another thread
112 #[error("Mutex poisoned")]
113 MutexPoisonError,
114}
115
116/// An error from trying to get a row from a Model
117#[derive(Debug)]
118pub enum TryGetError {
119 /// A database error was encountered as defined in [crate::DbErr]
120 DbErr(DbErr),
121 /// A null value was encountered
122 Null(String),
123}
124
125/// Connection Acquire error
126#[derive(Error, Debug, PartialEq, Eq, Copy, Clone)]
127pub enum ConnAcquireErr {
128 /// Connection pool timed out
129 #[error("Connection pool timed out")]
130 Timeout,
131 /// Connection closed
132 #[error("Connection closed")]
133 ConnectionClosed,
134}
135
136/// Runtime error
137#[derive(Error, Debug, Clone)]
138pub enum RuntimeErr {
139 /// SQLx Error
140 #[cfg(feature = "sqlx-dep")]
141 #[error("{0}")]
142 SqlxError(Arc<sqlx::error::Error>),
143 /// Rusqlite Error
144 #[cfg(feature = "rusqlite")]
145 #[error("{0}")]
146 Rusqlite(Arc<crate::driver::rusqlite::RusqliteError>),
147 /// Error generated from within SeaORM
148 #[error("{0}")]
149 Internal(String),
150}
151
152impl PartialEq for DbErr {
153 fn eq(&self, other: &Self) -> bool {
154 self.to_string() == other.to_string()
155 }
156}
157
158impl Eq for DbErr {}
159
160/// Error during `impl FromStr for Entity::Column`
161#[derive(Error, Debug)]
162#[error("Failed to match \"{0}\" as Column")]
163pub struct ColumnFromStrErr(pub String);
164
165#[allow(dead_code)]
166pub(crate) fn conn_err<T>(s: T) -> DbErr
167where
168 T: ToString,
169{
170 DbErr::Conn(RuntimeErr::Internal(s.to_string()))
171}
172
173#[allow(dead_code)]
174pub(crate) fn exec_err<T>(s: T) -> DbErr
175where
176 T: ToString,
177{
178 DbErr::Exec(RuntimeErr::Internal(s.to_string()))
179}
180
181#[allow(dead_code)]
182pub(crate) fn query_err<T>(s: T) -> DbErr
183where
184 T: ToString,
185{
186 DbErr::Query(RuntimeErr::Internal(s.to_string()))
187}
188
189#[allow(dead_code)]
190pub(crate) fn type_err<T>(s: T) -> DbErr
191where
192 T: ToString,
193{
194 DbErr::Type(s.to_string())
195}
196
197#[allow(dead_code)]
198pub(crate) fn json_err<T>(s: T) -> DbErr
199where
200 T: ToString,
201{
202 DbErr::Json(s.to_string())
203}
204
205/// A portable, backend-agnostic classification of the most common SQL
206/// constraint violations, produced by [`DbErr::sql_err`].
207///
208/// Only unique-key and foreign-key violations are recognized. For any other
209/// failure, or for backend-specific detail (SQLSTATE, driver error codes,
210/// check constraints, and so on), inspect the underlying driver error instead —
211/// see [`DbErr::sql_err`] for the pattern.
212#[derive(Error, Debug, Clone, PartialEq, Eq)]
213#[non_exhaustive]
214pub enum SqlErr {
215 /// Error for duplicate record in unique field or primary key field
216 #[error("Unique Constraint Violated: {0}")]
217 UniqueConstraintViolation(String),
218 /// Error for Foreign key constraint
219 #[error("Foreign Key Constraint Violated: {0}")]
220 ForeignKeyConstraintViolation(String),
221}
222
223#[allow(dead_code)]
224impl DbErr {
225 /// Classify this error as a portable [`SqlErr`] — a unique-key or
226 /// foreign-key constraint violation — returning `None` if it is neither, or
227 /// if it did not originate from a database driver.
228 ///
229 /// Only the two most common constraint violations are recognized, across
230 /// MySQL, Postgres and SQLite. For anything else (SQLSTATE codes, check
231 /// constraints, other driver-specific detail) match on the underlying
232 /// `RuntimeErr::SqlxError` and inspect the driver error yourself:
233 ///
234 /// ```
235 /// # #[cfg(feature = "sqlx-postgres")]
236 /// # fn example(err: sea_orm::DbErr) {
237 /// use sea_orm::{DbErr, RuntimeErr};
238 /// use std::ops::Deref;
239 ///
240 /// if let Some(sql_err) = err.sql_err() {
241 /// // Portable across MySQL / Postgres / SQLite.
242 /// eprintln!("constraint violation: {sql_err}");
243 /// } else if let DbErr::Query(RuntimeErr::SqlxError(e)) | DbErr::Exec(RuntimeErr::SqlxError(e)) =
244 /// &err
245 /// && let sea_orm::sqlx::Error::Database(db_err) = e.deref()
246 /// {
247 /// // Backend-specific: inspect the raw driver error.
248 /// eprintln!("SQLSTATE: {:?}", db_err.code());
249 /// }
250 /// # }
251 /// ```
252 pub fn sql_err(&self) -> Option<SqlErr> {
253 #[cfg(any(
254 feature = "sqlx-mysql",
255 feature = "sqlx-postgres",
256 feature = "sqlx-sqlite"
257 ))]
258 {
259 use std::ops::Deref;
260 if let DbErr::Exec(RuntimeErr::SqlxError(e)) | DbErr::Query(RuntimeErr::SqlxError(e)) =
261 self
262 && let sqlx::Error::Database(e) = e.deref()
263 {
264 let error_code = e.code().unwrap_or_default();
265 let _error_code_expanded = error_code.deref();
266 #[cfg(feature = "sqlx-mysql")]
267 if e.try_downcast_ref::<sqlx::mysql::MySqlDatabaseError>()
268 .is_some()
269 {
270 let error_number = e
271 .try_downcast_ref::<sqlx::mysql::MySqlDatabaseError>()?
272 .number();
273 match error_number {
274 // 1022 Can't write; duplicate key in table '%s'
275 // 1062 Duplicate entry '%s' for key %d
276 // 1169 Can't write, because of unique constraint, to table '%s'
277 // 1586 Duplicate entry '%s' for key '%s'
278 1022 | 1062 | 1169 | 1586 => {
279 return Some(SqlErr::UniqueConstraintViolation(e.message().into()));
280 }
281 // 1216 Cannot add or update a child row: a foreign key constraint fails
282 // 1217 Cannot delete or update a parent row: a foreign key constraint fails
283 // 1451 Cannot delete or update a parent row: a foreign key constraint fails (%s)
284 // 1452 Cannot add or update a child row: a foreign key constraint fails (%s)
285 // 1557 Upholding foreign key constraints for table '%s', entry '%s', key %d would lead to a duplicate entry
286 // 1761 Foreign key constraint for table '%s', record '%s' would lead to a duplicate entry in table '%s', key '%s'
287 // 1762 Foreign key constraint for table '%s', record '%s' would lead to a duplicate entry in a child table
288 1216 | 1217 | 1451 | 1452 | 1557 | 1761 | 1762 => {
289 return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()));
290 }
291 _ => return None,
292 }
293 }
294 #[cfg(feature = "sqlx-postgres")]
295 if e.try_downcast_ref::<sqlx::postgres::PgDatabaseError>()
296 .is_some()
297 {
298 match _error_code_expanded {
299 "23505" => {
300 return Some(SqlErr::UniqueConstraintViolation(e.message().into()));
301 }
302 "23503" => {
303 return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()));
304 }
305 _ => return None,
306 }
307 }
308 #[cfg(feature = "sqlx-sqlite")]
309 if e.try_downcast_ref::<sqlx::sqlite::SqliteError>().is_some() {
310 match _error_code_expanded {
311 // error code 1555 refers to the primary key's unique constraint violation
312 // error code 2067 refers to the UNIQUE unique constraint violation
313 "1555" | "2067" => {
314 return Some(SqlErr::UniqueConstraintViolation(e.message().into()));
315 }
316 "787" => {
317 return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()));
318 }
319 _ => return None,
320 }
321 }
322 }
323 }
324 #[cfg(feature = "rusqlite")]
325 if let DbErr::Exec(RuntimeErr::Rusqlite(err)) | DbErr::Query(RuntimeErr::Rusqlite(err)) =
326 self
327 {
328 use crate::driver::rusqlite::RusqliteError;
329 use std::ops::Deref;
330
331 if let RusqliteError::SqliteFailure(err, msg) = err.deref() {
332 match err.extended_code {
333 1555 | 2067 => {
334 return Some(SqlErr::UniqueConstraintViolation(
335 msg.to_owned().unwrap_or_else(|| err.to_string()),
336 ));
337 }
338 787 => {
339 return Some(SqlErr::ForeignKeyConstraintViolation(
340 msg.to_owned().unwrap_or_else(|| err.to_string()),
341 ));
342 }
343 _ => (),
344 }
345 }
346 }
347 None
348 }
349}