Introducing sqlx-error feature (#750)
* feat: Introducing feature "sqlx-error" Purpose of this feature is to not convert errors given from sqlx into strings to ease further analysis of the error and react to it accordingly. This implementation uses a feature switch and an additional error kind to avoid interfering with existing implementations without this feature enabled. See discussion https://github.com/SeaQL/sea-orm/discussions/709 * fix: Align feature "sqlx-error" with merged Migration error kind Due to the merge, an other error kind had been introduced and the DbErr became Eq and Clone, however Eq cannot easily be derived from, so I went back to PartialEq, and since the sqlx error does not implement clone, this was converted into an Arc, to allow cloning of the new kind too. * fix: Repairing failing jobs Several jobs had failed as I missed to correct the return values of a few methods in transaction.rs and had a wrong understanding of map_err at that point. * feat: realigning with latest changes in sea-orm, different approach Instead of the former approach to introduce a new error kind, now the existing error types get extended, for now only Exec and Query, because these are the most relevant for the requirement context. Afterwards it might still be possible to add some further detail information. See discussion https://github.com/SeaQL/sea-orm/discussions/709 * Update src/driver/sqlx_mysql.rs Integrating fixes done by @Sculas Co-authored-by: Sculas <contact@sculas.xyz> * Update src/driver/sqlx_postgres.rs Integrating fixes done by @Sculas Co-authored-by: Sculas <contact@sculas.xyz> * Update src/driver/sqlx_sqlite.rs Integrating fixes done by @Sculas Co-authored-by: Sculas <contact@sculas.xyz> * feat: reworking feature with thiserror Following the latest suggestions I changed the implementation to utilize `thiserror`. Now there are more error kinds to be able to see the different kinds directly in src/error.rs To ensure the behaviour is as expected, I also introduce a further test, which checks for a uniqueness failure. Co-authored-by: Sculas <contact@sculas.xyz>
This commit is contained in:
parent
13b53369e2
commit
fe6c40dd75
@ -42,6 +42,7 @@ uuid = { version = "^1", features = ["serde", "v4"], optional = true }
|
||||
ouroboros = "0.15"
|
||||
url = "^2.2"
|
||||
once_cell = "1.8"
|
||||
thiserror = "^1"
|
||||
|
||||
[dev-dependencies]
|
||||
smol = { version = "^1.2" }
|
||||
|
@ -26,10 +26,7 @@ macro_rules! impl_try_from_u64_err {
|
||||
($newtype: ident) => {
|
||||
impl sea_orm::TryFromU64 for $newtype {
|
||||
fn try_from_u64(_n: u64) -> Result<Self, sea_orm::DbErr> {
|
||||
Err(sea_orm::DbErr::Exec(format!(
|
||||
"{} cannot be converted from u64",
|
||||
stringify!($newtype)
|
||||
)))
|
||||
Err(sea_orm::ConvertFromU64(stringify!($newtype)))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
use std::marker::PhantomData;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "model")]
|
||||
@ -31,10 +31,7 @@ impl<T> From<AccountId<T>> for Uuid {
|
||||
|
||||
impl<T> sea_orm::TryFromU64 for AccountId<T> {
|
||||
fn try_from_u64(_n: u64) -> Result<Self, sea_orm::DbErr> {
|
||||
Err(sea_orm::DbErr::Exec(format!(
|
||||
"{} cannot be converted from u64",
|
||||
stringify!(AccountId<T>)
|
||||
)))
|
||||
Err(sea_orm::ConvertFromU64(stringify!(AccountId<T>)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ impl MockDatabaseTrait for MockDatabase {
|
||||
result: ExecResultHolder::Mock(std::mem::take(&mut self.exec_results[counter])),
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Exec("`exec_results` buffer is empty.".to_owned()))
|
||||
Err(DbErr::Query("`exec_results` buffer is empty.".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,6 +238,17 @@ impl DatabaseTransaction {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
fn map_err_ignore_not_found<T: std::fmt::Debug>(
|
||||
err: Result<Option<T>, sqlx::Error>,
|
||||
) -> Result<Option<T>, DbErr> {
|
||||
if let Err(sqlx::Error::RowNotFound) = err {
|
||||
Ok(None)
|
||||
} else {
|
||||
err.map_err(|e| sqlx_error_to_query_err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DatabaseTransaction {
|
||||
@ -258,13 +269,14 @@ impl ConnectionTrait for DatabaseTransaction {
|
||||
async fn execute(&self, stmt: Statement) -> Result<ExecResult, DbErr> {
|
||||
debug_print!("{}", stmt);
|
||||
|
||||
let _res = match &mut *self.conn.lock().await {
|
||||
match &mut *self.conn.lock().await {
|
||||
#[cfg(feature = "sqlx-mysql")]
|
||||
InnerConnection::MySql(conn) => {
|
||||
let query = crate::driver::sqlx_mysql::sqlx_query(&stmt);
|
||||
crate::metric::metric!(self.metric_callback, &stmt, {
|
||||
query.execute(conn).await.map(Into::into)
|
||||
})
|
||||
.map_err(sqlx_error_to_exec_err)
|
||||
}
|
||||
#[cfg(feature = "sqlx-postgres")]
|
||||
InnerConnection::Postgres(conn) => {
|
||||
@ -272,6 +284,7 @@ impl ConnectionTrait for DatabaseTransaction {
|
||||
crate::metric::metric!(self.metric_callback, &stmt, {
|
||||
query.execute(conn).await.map(Into::into)
|
||||
})
|
||||
.map_err(sqlx_error_to_exec_err)
|
||||
}
|
||||
#[cfg(feature = "sqlx-sqlite")]
|
||||
InnerConnection::Sqlite(conn) => {
|
||||
@ -279,14 +292,13 @@ impl ConnectionTrait for DatabaseTransaction {
|
||||
crate::metric::metric!(self.metric_callback, &stmt, {
|
||||
query.execute(conn).await.map(Into::into)
|
||||
})
|
||||
.map_err(sqlx_error_to_exec_err)
|
||||
}
|
||||
#[cfg(feature = "mock")]
|
||||
InnerConnection::Mock(conn) => return conn.execute(stmt),
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
_res.map_err(sqlx_error_to_exec_err)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "trace")]
|
||||
@ -294,32 +306,32 @@ impl ConnectionTrait for DatabaseTransaction {
|
||||
async fn query_one(&self, stmt: Statement) -> Result<Option<QueryResult>, DbErr> {
|
||||
debug_print!("{}", stmt);
|
||||
|
||||
let _res = match &mut *self.conn.lock().await {
|
||||
match &mut *self.conn.lock().await {
|
||||
#[cfg(feature = "sqlx-mysql")]
|
||||
InnerConnection::MySql(conn) => {
|
||||
let query = crate::driver::sqlx_mysql::sqlx_query(&stmt);
|
||||
query.fetch_one(conn).await.map(|row| Some(row.into()))
|
||||
Self::map_err_ignore_not_found(
|
||||
query.fetch_one(conn).await.map(|row| Some(row.into())),
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "sqlx-postgres")]
|
||||
InnerConnection::Postgres(conn) => {
|
||||
let query = crate::driver::sqlx_postgres::sqlx_query(&stmt);
|
||||
query.fetch_one(conn).await.map(|row| Some(row.into()))
|
||||
Self::map_err_ignore_not_found(
|
||||
query.fetch_one(conn).await.map(|row| Some(row.into())),
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "sqlx-sqlite")]
|
||||
InnerConnection::Sqlite(conn) => {
|
||||
let query = crate::driver::sqlx_sqlite::sqlx_query(&stmt);
|
||||
query.fetch_one(conn).await.map(|row| Some(row.into()))
|
||||
Self::map_err_ignore_not_found(
|
||||
query.fetch_one(conn).await.map(|row| Some(row.into())),
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "mock")]
|
||||
InnerConnection::Mock(conn) => return conn.query_one(stmt),
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
if let Err(sqlx::Error::RowNotFound) = _res {
|
||||
Ok(None)
|
||||
} else {
|
||||
_res.map_err(sqlx_error_to_query_err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,7 +340,7 @@ impl ConnectionTrait for DatabaseTransaction {
|
||||
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr> {
|
||||
debug_print!("{}", stmt);
|
||||
|
||||
let _res = match &mut *self.conn.lock().await {
|
||||
match &mut *self.conn.lock().await {
|
||||
#[cfg(feature = "sqlx-mysql")]
|
||||
InnerConnection::MySql(conn) => {
|
||||
let query = crate::driver::sqlx_mysql::sqlx_query(&stmt);
|
||||
@ -336,6 +348,7 @@ impl ConnectionTrait for DatabaseTransaction {
|
||||
.fetch_all(conn)
|
||||
.await
|
||||
.map(|rows| rows.into_iter().map(|r| r.into()).collect())
|
||||
.map_err(sqlx_error_to_query_err)
|
||||
}
|
||||
#[cfg(feature = "sqlx-postgres")]
|
||||
InnerConnection::Postgres(conn) => {
|
||||
@ -344,6 +357,7 @@ impl ConnectionTrait for DatabaseTransaction {
|
||||
.fetch_all(conn)
|
||||
.await
|
||||
.map(|rows| rows.into_iter().map(|r| r.into()).collect())
|
||||
.map_err(sqlx_error_to_query_err)
|
||||
}
|
||||
#[cfg(feature = "sqlx-sqlite")]
|
||||
InnerConnection::Sqlite(conn) => {
|
||||
@ -352,14 +366,13 @@ impl ConnectionTrait for DatabaseTransaction {
|
||||
.fetch_all(conn)
|
||||
.await
|
||||
.map(|rows| rows.into_iter().map(|r| r.into()).collect())
|
||||
.map_err(sqlx_error_to_query_err)
|
||||
}
|
||||
#[cfg(feature = "mock")]
|
||||
InnerConnection::Mock(conn) => return conn.query_all(stmt),
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => unreachable!(),
|
||||
};
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
_res.map_err(sqlx_error_to_query_err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,15 +2,15 @@ use crate::DbErr;
|
||||
|
||||
/// Converts an [sqlx::error] execution error to a [DbErr]
|
||||
pub fn sqlx_error_to_exec_err(err: sqlx::Error) -> DbErr {
|
||||
DbErr::Exec(err.to_string())
|
||||
DbErr::ExecSqlX(err)
|
||||
}
|
||||
|
||||
/// Converts an [sqlx::error] query error to a [DbErr]
|
||||
pub fn sqlx_error_to_query_err(err: sqlx::Error) -> DbErr {
|
||||
DbErr::Query(err.to_string())
|
||||
DbErr::QuerySqlX(err)
|
||||
}
|
||||
|
||||
/// Converts an [sqlx::error] connection error to a [DbErr]
|
||||
pub fn sqlx_error_to_conn_err(err: sqlx::Error) -> DbErr {
|
||||
DbErr::Conn(err.to_string())
|
||||
DbErr::ConnSqlX(err)
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ impl SqlxMySqlConnector {
|
||||
let mut opt = options
|
||||
.url
|
||||
.parse::<MySqlConnectOptions>()
|
||||
.map_err(|e| DbErr::Conn(e.to_string()))?;
|
||||
.map_err(|e| DbErr::ConnSqlX(e))?;
|
||||
use sqlx::ConnectOptions;
|
||||
if !options.sqlx_logging {
|
||||
opt.disable_statement_logging();
|
||||
@ -89,9 +89,7 @@ impl SqlxMySqlPoolConnection {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Exec(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,14 +105,12 @@ impl SqlxMySqlPoolConnection {
|
||||
Ok(row) => Ok(Some(row.into())),
|
||||
Err(err) => match err {
|
||||
sqlx::Error::RowNotFound => Ok(None),
|
||||
_ => Err(DbErr::Query(err.to_string())),
|
||||
_ => Err(sqlx_error_to_query_err(err)),
|
||||
},
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,9 +128,7 @@ impl SqlxMySqlPoolConnection {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,9 +144,7 @@ impl SqlxMySqlPoolConnection {
|
||||
self.metric_callback.clone(),
|
||||
)))
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,9 +154,7 @@ impl SqlxMySqlPoolConnection {
|
||||
if let Ok(conn) = self.pool.acquire().await {
|
||||
DatabaseTransaction::new_mysql(conn, self.metric_callback.clone()).await
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,9 +175,7 @@ impl SqlxMySqlPoolConnection {
|
||||
.map_err(|e| TransactionError::Connection(e))?;
|
||||
transaction.run(callback).await
|
||||
} else {
|
||||
Err(TransactionError::Connection(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
)))
|
||||
Err(TransactionError::Connection(DbErr::ConnFromPool))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ impl SqlxPostgresConnector {
|
||||
let mut opt = options
|
||||
.url
|
||||
.parse::<PgConnectOptions>()
|
||||
.map_err(|e| DbErr::Conn(e.to_string()))?;
|
||||
.map_err(|e| DbErr::ConnSqlX(e))?;
|
||||
use sqlx::ConnectOptions;
|
||||
if !options.sqlx_logging {
|
||||
opt.disable_statement_logging();
|
||||
@ -89,9 +89,7 @@ impl SqlxPostgresPoolConnection {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Exec(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,14 +105,12 @@ impl SqlxPostgresPoolConnection {
|
||||
Ok(row) => Ok(Some(row.into())),
|
||||
Err(err) => match err {
|
||||
sqlx::Error::RowNotFound => Ok(None),
|
||||
_ => Err(DbErr::Query(err.to_string())),
|
||||
_ => Err(sqlx_error_to_query_err(err)),
|
||||
},
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,9 +128,7 @@ impl SqlxPostgresPoolConnection {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,9 +144,7 @@ impl SqlxPostgresPoolConnection {
|
||||
self.metric_callback.clone(),
|
||||
)))
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,9 +154,7 @@ impl SqlxPostgresPoolConnection {
|
||||
if let Ok(conn) = self.pool.acquire().await {
|
||||
DatabaseTransaction::new_postgres(conn, self.metric_callback.clone()).await
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,9 +175,7 @@ impl SqlxPostgresPoolConnection {
|
||||
.map_err(|e| TransactionError::Connection(e))?;
|
||||
transaction.run(callback).await
|
||||
} else {
|
||||
Err(TransactionError::Connection(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
)))
|
||||
Err(TransactionError::Connection(DbErr::ConnFromPool))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ impl SqlxSqliteConnector {
|
||||
let mut opt = options
|
||||
.url
|
||||
.parse::<SqliteConnectOptions>()
|
||||
.map_err(|e| DbErr::Conn(e.to_string()))?;
|
||||
.map_err(|e| DbErr::ConnSqlX(e))?;
|
||||
if options.sqlcipher_key.is_some() {
|
||||
opt = opt.pragma("key", options.sqlcipher_key.clone().unwrap());
|
||||
}
|
||||
@ -96,9 +96,7 @@ impl SqlxSqlitePoolConnection {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Exec(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,14 +112,12 @@ impl SqlxSqlitePoolConnection {
|
||||
Ok(row) => Ok(Some(row.into())),
|
||||
Err(err) => match err {
|
||||
sqlx::Error::RowNotFound => Ok(None),
|
||||
_ => Err(DbErr::Query(err.to_string())),
|
||||
_ => Err(sqlx_error_to_query_err(err)),
|
||||
},
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,9 +135,7 @@ impl SqlxSqlitePoolConnection {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,9 +151,7 @@ impl SqlxSqlitePoolConnection {
|
||||
self.metric_callback.clone(),
|
||||
)))
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,9 +161,7 @@ impl SqlxSqlitePoolConnection {
|
||||
if let Ok(conn) = self.pool.acquire().await {
|
||||
DatabaseTransaction::new_sqlite(conn, self.metric_callback.clone()).await
|
||||
} else {
|
||||
Err(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
))
|
||||
Err(DbErr::ConnFromPool)
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,9 +182,7 @@ impl SqlxSqlitePoolConnection {
|
||||
.map_err(|e| TransactionError::Connection(e))?;
|
||||
transaction.run(callback).await
|
||||
} else {
|
||||
Err(TransactionError::Connection(DbErr::Query(
|
||||
"Failed to acquire connection from pool.".to_owned(),
|
||||
)))
|
||||
Err(TransactionError::Connection(DbErr::ConnFromPool))
|
||||
}
|
||||
}
|
||||
|
||||
|
58
src/error.rs
58
src/error.rs
@ -1,38 +1,64 @@
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
use sqlx::error::Error as SqlxError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// An error from unsuccessful database operations
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DbErr {
|
||||
/// This error happens, when a pool was not able to create a connection
|
||||
#[error("Failed to acquire connection from pool.")]
|
||||
ConnFromPool,
|
||||
/// Error in case of invalid type conversion attempts
|
||||
#[error("fail to convert '{0}' into '{1}'")]
|
||||
CannotConvertInto(String, String),
|
||||
/// Error in case of invalid type conversion from an u64
|
||||
#[error("{0} cannot be converted from u64")]
|
||||
ConvertFromU64(String),
|
||||
/// After an insert statement it was impossible to retrieve the last_insert_id
|
||||
#[error("Fail to unpack last_insert_id")]
|
||||
InsertCouldNotUnpackInsertId,
|
||||
/// When updating, a model should know it's primary key to check
|
||||
/// if the record has been correctly updated, otherwise this error will occur
|
||||
#[error("Fail to get primary key from model")]
|
||||
UpdateCouldNotGetPrimaryKey,
|
||||
/// There was a problem with the database connection
|
||||
#[error("Connection Error: {0}")]
|
||||
Conn(String),
|
||||
/// There was a problem with the database connection from sqlx
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
#[error("Connection Error: {0}")]
|
||||
ConnSqlX(#[source] SqlxError),
|
||||
/// An operation did not execute successfully
|
||||
Exec(String),
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
#[error("Execution Error: {0}")]
|
||||
ExecSqlX(#[source] SqlxError),
|
||||
/// An error occurred while performing a query, with more details from sqlx
|
||||
#[cfg(feature = "sqlx-dep")]
|
||||
#[error("Query Error: {0}")]
|
||||
QuerySqlX(#[source] SqlxError),
|
||||
/// An error occurred while performing a query
|
||||
#[error("Query Error: {0}")]
|
||||
Query(String),
|
||||
/// The record was not found in the database
|
||||
#[error("RecordNotFound Error: {0}")]
|
||||
RecordNotFound(String),
|
||||
/// A custom error
|
||||
#[error("Custom Error: {0}")]
|
||||
Custom(String),
|
||||
/// Error occurred while parsing value as target type
|
||||
#[error("Type Error: {0}")]
|
||||
Type(String),
|
||||
/// Error occurred while parsing json value as target type
|
||||
#[error("Json Error: {0}")]
|
||||
Json(String),
|
||||
/// A migration error
|
||||
#[error("Migration Error: {0}")]
|
||||
Migration(String),
|
||||
}
|
||||
|
||||
impl std::error::Error for DbErr {}
|
||||
|
||||
impl std::fmt::Display for DbErr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Conn(s) => write!(f, "Connection Error: {}", s),
|
||||
Self::Exec(s) => write!(f, "Execution Error: {}", s),
|
||||
Self::Query(s) => write!(f, "Query Error: {}", s),
|
||||
Self::RecordNotFound(s) => write!(f, "RecordNotFound Error: {}", s),
|
||||
Self::Custom(s) => write!(f, "Custom Error: {}", s),
|
||||
Self::Type(s) => write!(f, "Type Error: {}", s),
|
||||
Self::Json(s) => write!(f, "Json Error: {}", s),
|
||||
Self::Migration(s) => write!(f, "Migration Error: {}", s),
|
||||
}
|
||||
impl PartialEq for DbErr {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.to_string() == other.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,7 +130,7 @@ where
|
||||
Some(value_tuple) => FromValueTuple::from_value_tuple(value_tuple),
|
||||
None => match last_insert_id_opt {
|
||||
Some(last_insert_id) => last_insert_id,
|
||||
None => return Err(DbErr::Exec("Fail to unpack last_insert_id".to_owned())),
|
||||
None => return Err(DbErr::InsertCouldNotUnpackInsertId),
|
||||
},
|
||||
};
|
||||
Ok(InsertResult { last_insert_id })
|
||||
@ -176,6 +176,8 @@ where
|
||||
};
|
||||
match found {
|
||||
Some(model) => Ok(model),
|
||||
None => Err(DbErr::Exec("Failed to find inserted item".to_owned())),
|
||||
None => Err(DbErr::RecordNotFound(
|
||||
"Failed to find inserted item".to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -379,8 +379,9 @@ impl TryGetable for Decimal {
|
||||
use rust_decimal::prelude::FromPrimitive;
|
||||
match val {
|
||||
Some(v) => Decimal::from_f64(v).ok_or_else(|| {
|
||||
TryGetError::DbErr(DbErr::Query(
|
||||
"Failed to convert f64 into Decimal".to_owned(),
|
||||
TryGetError::DbErr(DbErr::CannotConvertInto(
|
||||
"f64".to_owned(),
|
||||
"Decimal".to_owned(),
|
||||
))
|
||||
}),
|
||||
None => Err(TryGetError::Null(column)),
|
||||
@ -708,10 +709,7 @@ macro_rules! try_from_u64_err {
|
||||
( $type: ty ) => {
|
||||
impl TryFromU64 for $type {
|
||||
fn try_from_u64(_: u64) -> Result<Self, DbErr> {
|
||||
Err(DbErr::Exec(format!(
|
||||
"{} cannot be converted from u64",
|
||||
stringify!($type)
|
||||
)))
|
||||
Err(DbErr::ConvertFromU64(stringify!($type).to_string()))
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -722,10 +720,7 @@ macro_rules! try_from_u64_err {
|
||||
$( $gen_type: TryFromU64, )*
|
||||
{
|
||||
fn try_from_u64(_: u64) -> Result<Self, DbErr> {
|
||||
Err(DbErr::Exec(format!(
|
||||
"{} cannot be converted from u64",
|
||||
stringify!(($($gen_type,)*))
|
||||
)))
|
||||
Err(DbErr::ConvertFromU64(stringify!($($gen_type,)*).to_string()))
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -744,11 +739,7 @@ macro_rules! try_from_u64_numeric {
|
||||
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
|
||||
use std::convert::TryInto;
|
||||
n.try_into().map_err(|_| {
|
||||
DbErr::Exec(format!(
|
||||
"fail to convert '{}' into '{}'",
|
||||
n,
|
||||
stringify!($type)
|
||||
))
|
||||
DbErr::CannotConvertInto(n.to_string(), stringify!($type).to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -828,9 +819,11 @@ mod tests {
|
||||
#[test]
|
||||
fn from_try_get_error() {
|
||||
// TryGetError::DbErr
|
||||
let expected = DbErr::Query("expected error message".to_owned());
|
||||
let try_get_error = TryGetError::DbErr(expected.clone());
|
||||
assert_eq!(DbErr::from(try_get_error), expected);
|
||||
let try_get_error = TryGetError::DbErr(DbErr::Query("expected error message".to_owned()));
|
||||
assert_eq!(
|
||||
DbErr::from(try_get_error),
|
||||
DbErr::Query("expected error message".to_owned())
|
||||
);
|
||||
|
||||
// TryGetError::Null
|
||||
let try_get_error = TryGetError::Null("column".to_owned());
|
||||
|
@ -122,7 +122,7 @@ where
|
||||
Updater::new(query).check_record_exists().exec(db).await?;
|
||||
let primary_key_value = match model.get_primary_key_value() {
|
||||
Some(val) => FromValueTuple::from_value_tuple(val),
|
||||
None => return Err(DbErr::Exec("Fail to get primary key from model".to_owned())),
|
||||
None => return Err(DbErr::UpdateCouldNotGetPrimaryKey),
|
||||
};
|
||||
let found = <A::Entity as EntityTrait>::find_by_id(primary_key_value)
|
||||
.one(db)
|
||||
@ -130,7 +130,9 @@ where
|
||||
// If we cannot select the updated row from db by the cached primary key
|
||||
match found {
|
||||
Some(model) => Ok(model),
|
||||
None => Err(DbErr::Exec("Failed to find inserted item".to_owned())),
|
||||
None => Err(DbErr::RecordNotFound(
|
||||
"Failed to find inserted item".to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
56
tests/crud/error.rs
Normal file
56
tests/crud/error.rs
Normal file
@ -0,0 +1,56 @@
|
||||
pub use super::*;
|
||||
use rust_decimal_macros::dec;
|
||||
use sea_orm::DbErr;
|
||||
#[cfg(any(
|
||||
feature = "sqlx-mysql",
|
||||
feature = "sqlx-sqlite",
|
||||
feature = "sqlx-postgres"
|
||||
))]
|
||||
use sqlx::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn test_cake_error_sqlx(db: &DbConn) {
|
||||
let mud_cake = cake::ActiveModel {
|
||||
name: Set("Moldy Cake".to_owned()),
|
||||
price: Set(dec!(10.25)),
|
||||
gluten_free: Set(false),
|
||||
serial: Set(Uuid::new_v4()),
|
||||
bakery_id: Set(None),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let cake = mud_cake.save(db).await.expect("could not insert cake");
|
||||
|
||||
// if compiling without sqlx, this assignment will complain,
|
||||
// but the whole test is useless in that case anyway.
|
||||
#[allow(unused_variables)]
|
||||
let error: DbErr = cake
|
||||
.into_active_model()
|
||||
.insert(db)
|
||||
.await
|
||||
.expect_err("inserting should fail due to duplicate primary key");
|
||||
|
||||
#[cfg(any(feature = "sqlx-mysql", feature = "sqlx-sqlite"))]
|
||||
match error {
|
||||
DbErr::ExecSqlX(sqlx_error) => match sqlx_error {
|
||||
Error::Database(e) => {
|
||||
#[cfg(feature = "sqlx-mysql")]
|
||||
assert_eq!(e.code().unwrap(), "23000");
|
||||
#[cfg(feature = "sqlx-sqlite")]
|
||||
assert_eq!(e.code().unwrap(), "1555");
|
||||
}
|
||||
_ => panic!("Unexpected sqlx-error kind"),
|
||||
},
|
||||
_ => panic!("Unexpected Error kind"),
|
||||
}
|
||||
#[cfg(feature = "sqlx-postgres")]
|
||||
match error {
|
||||
DbErr::QuerySqlX(sqlx_error) => match sqlx_error {
|
||||
Error::Database(e) => {
|
||||
assert_eq!(e.code().unwrap(), "23505");
|
||||
}
|
||||
_ => panic!("Unexpected sqlx-error kind"),
|
||||
},
|
||||
_ => panic!("Unexpected Error kind"),
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ pub mod create_cake;
|
||||
pub mod create_lineitem;
|
||||
pub mod create_order;
|
||||
pub mod deletes;
|
||||
pub mod error;
|
||||
pub mod updates;
|
||||
|
||||
pub use create_baker::*;
|
||||
@ -10,6 +11,7 @@ pub use create_cake::*;
|
||||
pub use create_lineitem::*;
|
||||
pub use create_order::*;
|
||||
pub use deletes::*;
|
||||
pub use error::*;
|
||||
pub use updates::*;
|
||||
|
||||
pub use super::common::bakery_chain::*;
|
||||
|
@ -35,5 +35,6 @@ pub async fn create_entities(db: &DatabaseConnection) {
|
||||
test_update_deleted_customer(db).await;
|
||||
|
||||
test_delete_cake(db).await;
|
||||
test_cake_error_sqlx(db).await;
|
||||
test_delete_bakery(db).await;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user