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 thiserror::Error;
14
15/// An error from unsuccessful database operations
16#[derive(Error, Debug)]
17pub enum DbErr {
18    /// This error can happen when the connection pool is fully-utilized
19    #[error("Failed to acquire connection from pool: {0}")]
20    ConnectionAcquire(#[source] ConnAcquireErr),
21    /// Runtime type conversion error
22    #[error("Error converting `{from}` into `{into}`: {source}")]
23    TryIntoErr {
24        /// From type
25        from: &'static str,
26        /// Into type
27        into: &'static str,
28        /// TryError
29        source: Box<dyn std::error::Error + Send + Sync>,
30    },
31    /// There was a problem with the database connection
32    #[error("Connection Error: {0}")]
33    Conn(#[source] RuntimeErr),
34    /// An operation did not execute successfully
35    #[error("Execution Error: {0}")]
36    Exec(#[source] RuntimeErr),
37    /// An error occurred while performing a query
38    #[error("Query Error: {0}")]
39    Query(#[source] RuntimeErr),
40    /// Type error: the specified type cannot be converted from u64. This is not a runtime error.
41    #[error("Type '{0}' cannot be converted from u64")]
42    ConvertFromU64(&'static str),
43    /// After an insert statement it was impossible to retrieve the last_insert_id
44    #[error("Failed to unpack last_insert_id")]
45    UnpackInsertId,
46    /// When updating, a model should know its primary key to check
47    /// if the record has been correctly updated, otherwise this error will occur
48    #[error("Failed to get primary key from model")]
49    UpdateGetPrimaryKey,
50    /// The record was not found in the database
51    #[error("RecordNotFound Error: {0}")]
52    RecordNotFound(String),
53    /// Thrown by `TryFrom<ActiveModel>`, which assumes all attributes are set/unchanged
54    #[error("Attribute {0} is NotSet")]
55    AttrNotSet(String),
56    /// A custom error
57    #[error("Custom Error: {0}")]
58    Custom(String),
59    /// Error occurred while parsing value as target type
60    #[error("Type Error: {0}")]
61    Type(String),
62    /// Error occurred while parsing json value as target type
63    #[error("Json Error: {0}")]
64    Json(String),
65    /// A migration error
66    #[error("Migration Error: {0}")]
67    Migration(String),
68    /// None of the records are inserted,
69    /// that probably means all of them conflict with existing records in the table
70    #[error("None of the records are inserted")]
71    RecordNotInserted,
72    /// None of the records are updated, that means a WHERE condition has no matches.
73    /// May be the table is empty or the record does not exist
74    #[error("None of the records are updated")]
75    RecordNotUpdated,
76}
77
78/// Connection Acquire error
79#[derive(Error, Debug, PartialEq, Eq)]
80pub enum ConnAcquireErr {
81    /// Connection pool timed out
82    #[error("Connection pool timed out")]
83    Timeout,
84    /// Connection closed
85    #[error("Connection closed")]
86    ConnectionClosed,
87}
88
89/// Runtime error
90#[derive(Error, Debug)]
91pub enum RuntimeErr {
92    /// SQLx Error
93    #[cfg(feature = "sqlx-dep")]
94    #[error("{0}")]
95    SqlxError(#[source] sqlx::error::Error),
96    /// Error generated from within SeaORM
97    #[error("{0}")]
98    Internal(String),
99}
100
101impl PartialEq for DbErr {
102    fn eq(&self, other: &Self) -> bool {
103        self.to_string() == other.to_string()
104    }
105}
106
107impl Eq for DbErr {}
108
109/// Error during `impl FromStr for Entity::Column`
110#[derive(Error, Debug)]
111#[error("Failed to match \"{0}\" as Column")]
112pub struct ColumnFromStrErr(pub String);
113
114#[allow(dead_code)]
115pub(crate) fn conn_err<T>(s: T) -> DbErr
116where
117    T: ToString,
118{
119    DbErr::Conn(RuntimeErr::Internal(s.to_string()))
120}
121
122#[allow(dead_code)]
123pub(crate) fn exec_err<T>(s: T) -> DbErr
124where
125    T: ToString,
126{
127    DbErr::Exec(RuntimeErr::Internal(s.to_string()))
128}
129
130#[allow(dead_code)]
131pub(crate) fn query_err<T>(s: T) -> DbErr
132where
133    T: ToString,
134{
135    DbErr::Query(RuntimeErr::Internal(s.to_string()))
136}
137
138#[allow(dead_code)]
139pub(crate) fn type_err<T>(s: T) -> DbErr
140where
141    T: ToString,
142{
143    DbErr::Type(s.to_string())
144}
145
146#[allow(dead_code)]
147pub(crate) fn json_err<T>(s: T) -> DbErr
148where
149    T: ToString,
150{
151    DbErr::Json(s.to_string())
152}
153
154/// An error from unsuccessful SQL query
155#[derive(Error, Debug, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum SqlErr {
158    /// Error for duplicate record in unique field or primary key field
159    #[error("Unique Constraint Violated: {0}")]
160    UniqueConstraintViolation(String),
161    /// Error for Foreign key constraint
162    #[error("Foreign Key Constraint Violated: {0}")]
163    ForeignKeyConstraintViolation(String),
164}
165
166#[allow(dead_code)]
167impl DbErr {
168    /// Convert generic DbErr by sqlx to SqlErr, return none if the error is not any type of SqlErr
169    pub fn sql_err(&self) -> Option<SqlErr> {
170        #[cfg(any(
171            feature = "sqlx-mysql",
172            feature = "sqlx-postgres",
173            feature = "sqlx-sqlite"
174        ))]
175        {
176            use std::ops::Deref;
177            if let DbErr::Exec(RuntimeErr::SqlxError(sqlx::Error::Database(e)))
178            | DbErr::Query(RuntimeErr::SqlxError(sqlx::Error::Database(e))) = self
179            {
180                let error_code = e.code().unwrap_or_default();
181                let _error_code_expanded = error_code.deref();
182                #[cfg(feature = "sqlx-mysql")]
183                if e.try_downcast_ref::<sqlx::mysql::MySqlDatabaseError>()
184                    .is_some()
185                {
186                    let error_number = e
187                        .try_downcast_ref::<sqlx::mysql::MySqlDatabaseError>()?
188                        .number();
189                    match error_number {
190                        // 1022 Can't write; duplicate key in table '%s'
191                        // 1062 Duplicate entry '%s' for key %d
192                        // 1169 Can't write, because of unique constraint, to table '%s'
193                        // 1586 Duplicate entry '%s' for key '%s'
194                        1022 | 1062 | 1169 | 1586 => {
195                            return Some(SqlErr::UniqueConstraintViolation(e.message().into()))
196                        }
197                        // 1216 Cannot add or update a child row: a foreign key constraint fails
198                        // 1217 Cannot delete or update a parent row: a foreign key constraint fails
199                        // 1451 Cannot delete or update a parent row: a foreign key constraint fails (%s)
200                        // 1452 Cannot add or update a child row: a foreign key constraint fails (%s)
201                        // 1557 Upholding foreign key constraints for table '%s', entry '%s', key %d would lead to a duplicate entry
202                        // 1761 Foreign key constraint for table '%s', record '%s' would lead to a duplicate entry in table '%s', key '%s'
203                        // 1762 Foreign key constraint for table '%s', record '%s' would lead to a duplicate entry in a child table
204                        1216 | 1217 | 1451 | 1452 | 1557 | 1761 | 1762 => {
205                            return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()))
206                        }
207                        _ => return None,
208                    }
209                }
210                #[cfg(feature = "sqlx-postgres")]
211                if e.try_downcast_ref::<sqlx::postgres::PgDatabaseError>()
212                    .is_some()
213                {
214                    match _error_code_expanded {
215                        "23505" => {
216                            return Some(SqlErr::UniqueConstraintViolation(e.message().into()))
217                        }
218                        "23503" => {
219                            return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()))
220                        }
221                        _ => return None,
222                    }
223                }
224                #[cfg(feature = "sqlx-sqlite")]
225                if e.try_downcast_ref::<sqlx::sqlite::SqliteError>().is_some() {
226                    match _error_code_expanded {
227                        // error code 1555 refers to the primary key's unique constraint violation
228                        // error code 2067 refers to the UNIQUE unique constraint violation
229                        "1555" | "2067" => {
230                            return Some(SqlErr::UniqueConstraintViolation(e.message().into()))
231                        }
232                        "787" => {
233                            return Some(SqlErr::ForeignKeyConstraintViolation(e.message().into()))
234                        }
235                        _ => return None,
236                    }
237                }
238            }
239        }
240        None
241    }
242}