Merge pull request #1002 from SeaQL/better-errors

Better error system
This commit is contained in:
Chris Tsang 2022-10-07 00:24:41 +08:00 committed by GitHub
commit 5d752e60b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 242 additions and 163 deletions

View File

@ -43,6 +43,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" }

View File

@ -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::DbErr::ConvertFromU64(stringify!($newtype)))
}
}
};

View File

@ -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::DbErr::ConvertFromU64(stringify!(AccountId<T>)))
}
}

View File

@ -71,7 +71,7 @@ pub fn impl_default_as_str(ident: &Ident, data: &Data) -> syn::Result<TokenStrea
))
}
/// Implement a column using for an enum using [DeriveColumn](sea_orm::DeriveColumn)
/// Implement a column for an enum using [DeriveColumn](sea_orm::DeriveColumn)
pub fn impl_col_from_str(ident: &Ident, data: &Data) -> syn::Result<TokenStream> {
let data_enum = match data {
Data::Enum(data_enum) => data_enum,
@ -99,7 +99,7 @@ pub fn impl_col_from_str(ident: &Ident, data: &Data) -> syn::Result<TokenStream>
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
#(#columns),*,
_ => Err(sea_orm::ColumnFromStrErr(format!("Failed to parse '{}' as `{}`", s, stringify!(#ident)))),
_ => Err(sea_orm::ColumnFromStrErr(s.to_owned())),
}
}
}

View File

@ -116,7 +116,9 @@ impl ConnectionTrait for DatabaseConnection {
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.execute(stmt).await,
#[cfg(feature = "mock")]
DatabaseConnection::MockDatabaseConnection(conn) => conn.execute(stmt),
DatabaseConnection::Disconnected => Err(DbErr::Conn("Disconnected".to_owned())),
DatabaseConnection::Disconnected => {
Err(DbErr::Conn(RuntimeErr::Internal("Disconnected".to_owned())))
}
}
}
@ -132,7 +134,9 @@ impl ConnectionTrait for DatabaseConnection {
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_one(stmt).await,
#[cfg(feature = "mock")]
DatabaseConnection::MockDatabaseConnection(conn) => conn.query_one(stmt),
DatabaseConnection::Disconnected => Err(DbErr::Conn("Disconnected".to_owned())),
DatabaseConnection::Disconnected => {
Err(DbErr::Conn(RuntimeErr::Internal("Disconnected".to_owned())))
}
}
}
@ -148,7 +152,9 @@ impl ConnectionTrait for DatabaseConnection {
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_all(stmt).await,
#[cfg(feature = "mock")]
DatabaseConnection::MockDatabaseConnection(conn) => conn.query_all(stmt),
DatabaseConnection::Disconnected => Err(DbErr::Conn("Disconnected".to_owned())),
DatabaseConnection::Disconnected => {
Err(DbErr::Conn(RuntimeErr::Internal("Disconnected".to_owned())))
}
}
}

View File

@ -102,7 +102,9 @@ 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::Exec(RuntimeErr::Internal(
"`exec_results` buffer is empty.".to_owned(),
)))
}
}
@ -121,7 +123,9 @@ impl MockDatabaseTrait for MockDatabase {
})
.collect())
} else {
Err(DbErr::Query("`query_results` buffer is empty.".to_owned()))
Err(DbErr::Query(RuntimeErr::Internal(
"`query_results` buffer is empty.".to_owned(),
)))
}
}
@ -176,7 +180,7 @@ impl MockRow {
where
T: ValueType,
{
T::try_from(self.values.get(col).unwrap().clone()).map_err(|e| DbErr::Query(e.to_string()))
T::try_from(self.values.get(col).unwrap().clone()).map_err(|e| DbErr::Type(e.to_string()))
}
/// An iterator over the keys and values of a mock row

View File

@ -18,7 +18,7 @@ pub use stream::*;
use tracing::instrument;
pub use transaction::*;
use crate::DbErr;
use crate::{DbErr, RuntimeErr};
/// Defines a database
#[derive(Debug, Default)]
@ -75,10 +75,10 @@ impl Database {
if crate::MockDatabaseConnector::accepts(&opt.url) {
return crate::MockDatabaseConnector::connect(&opt.url).await;
}
Err(DbErr::Conn(format!(
Err(DbErr::Conn(RuntimeErr::Internal(format!(
"The connection string '{}' has no supporting driver.",
opt.url
)))
))))
}
}

View File

@ -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)
}
}
}

View File

@ -1,16 +1,16 @@
use crate::DbErr;
use crate::{DbErr, RuntimeErr};
/// 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::Exec(RuntimeErr::SqlxError(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::Query(RuntimeErr::SqlxError(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::Conn(RuntimeErr::SqlxError(err))
}

View File

@ -45,7 +45,7 @@ impl SqlxMySqlConnector {
let mut opt = options
.url
.parse::<MySqlConnectOptions>()
.map_err(|e| DbErr::Conn(e.to_string()))?;
.map_err(sqlx_error_to_conn_err)?;
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::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire)
}
}
@ -132,9 +128,7 @@ impl SqlxMySqlPoolConnection {
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnectionAcquire)
}
}
@ -150,9 +144,7 @@ impl SqlxMySqlPoolConnection {
self.metric_callback.clone(),
)))
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire))
}
}

View File

@ -45,7 +45,7 @@ impl SqlxPostgresConnector {
let mut opt = options
.url
.parse::<PgConnectOptions>()
.map_err(|e| DbErr::Conn(e.to_string()))?;
.map_err(sqlx_error_to_conn_err)?;
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::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire)
}
}
@ -132,9 +128,7 @@ impl SqlxPostgresPoolConnection {
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnectionAcquire)
}
}
@ -150,9 +144,7 @@ impl SqlxPostgresPoolConnection {
self.metric_callback.clone(),
)))
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire))
}
}

View File

@ -46,7 +46,7 @@ impl SqlxSqliteConnector {
let mut opt = options
.url
.parse::<SqliteConnectOptions>()
.map_err(|e| DbErr::Conn(e.to_string()))?;
.map_err(sqlx_error_to_conn_err)?;
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::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire)
}
}
@ -139,9 +135,7 @@ impl SqlxSqlitePoolConnection {
}
})
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnectionAcquire)
}
}
@ -157,9 +151,7 @@ impl SqlxSqlitePoolConnection {
self.metric_callback.clone(),
)))
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
Err(DbErr::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire)
}
}
@ -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::ConnectionAcquire))
}
}

View File

@ -1,49 +1,80 @@
#[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 can happen when the connection pool is fully-utilized
#[error("Failed to acquire connection from pool")]
ConnectionAcquire,
/// Runtime type conversion error
#[error("Error converting `{from}` into `{into}`: {source}")]
TryIntoErr {
/// From type
from: &'static str,
/// Into type
into: &'static str,
/// TryError
source: Box<dyn std::error::Error + Send + Sync>,
},
/// There was a problem with the database connection
Conn(String),
#[error("Connection Error: {0}")]
Conn(#[source] RuntimeErr),
/// An operation did not execute successfully
Exec(String),
#[error("Execution Error: {0}")]
Exec(#[source] RuntimeErr),
/// An error occurred while performing a query
Query(String),
#[error("Query Error: {0}")]
Query(#[source] RuntimeErr),
/// Type error: the specified type cannot be converted from u64. This is not a runtime error.
#[error("Type '{0}' cannot be converted from u64")]
ConvertFromU64(&'static str),
/// After an insert statement it was impossible to retrieve the last_insert_id
#[error("Failed to unpack last_insert_id")]
UnpackInsertId,
/// 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("Failed to get primary key from model")]
UpdateGetPrimeryKey,
/// 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 {}
/// Runtime error
#[derive(Error, Debug)]
pub enum RuntimeErr {
/// SQLx Error
#[cfg(feature = "sqlx-dep")]
#[error("{0}")]
SqlxError(SqlxError),
/// Error generated from within SeaORM
#[error("{0}")]
Internal(String),
}
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()
}
}
/// An error from a failed column operation when trying to convert the column to a string
#[derive(Debug, Clone)]
impl Eq for DbErr {}
/// Error during `impl FromStr for Entity::Column`
#[derive(Error, Debug)]
#[error("Failed to match \"{0}\" as Column")]
pub struct ColumnFromStrErr(pub String);
impl std::error::Error for ColumnFromStrErr {}
impl std::fmt::Display for ColumnFromStrErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0.as_str())
}
}

View File

@ -128,7 +128,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::UnpackInsertId),
},
};
Ok(InsertResult { last_insert_id })
@ -168,6 +168,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(),
)),
}
}

View File

@ -41,7 +41,7 @@ impl From<TryGetError> for DbErr {
match e {
TryGetError::DbErr(e) => e,
TryGetError::Null(s) => {
DbErr::Query(format!("error occurred while decoding {}: Null", s))
DbErr::Type(format!("A null value was encountered while decoding {}", s))
}
}
}
@ -376,12 +376,13 @@ impl TryGetable for Decimal {
let val: Option<f64> = row
.try_get(column.as_str())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
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(),
))
Some(v) => Decimal::try_from(v).map_err(|e| {
TryGetError::DbErr(DbErr::TryIntoErr {
from: "f64",
into: "Decimal",
source: Box::new(e),
})
}),
None => Err(TryGetError::Null(column)),
}
@ -626,7 +627,7 @@ where
fn try_get_many_with_slice_len_of(len: usize, cols: &[String]) -> Result<(), TryGetError> {
if cols.len() < len {
Err(TryGetError::DbErr(DbErr::Query(format!(
Err(TryGetError::DbErr(DbErr::Type(format!(
"Expect {} column names supplied but got slice of length {}",
len,
cols.len()
@ -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)))
}
}
};
@ -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,)*)))
}
}
};
@ -743,12 +738,10 @@ macro_rules! try_from_u64_numeric {
impl TryFromU64 for $type {
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)
))
n.try_into().map_err(|e| DbErr::TryIntoErr {
from: stringify!(u64),
into: stringify!($type),
source: Box::new(e),
})
}
}
@ -823,18 +816,22 @@ try_from_u64_err!(uuid::Uuid);
#[cfg(test)]
mod tests {
use super::TryGetError;
use crate::error::DbErr;
use crate::error::*;
#[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(RuntimeErr::Internal(
"expected error message".to_owned(),
)));
assert_eq!(
DbErr::from(try_get_error),
DbErr::Query(RuntimeErr::Internal("expected error message".to_owned()))
);
// TryGetError::Null
let try_get_error = TryGetError::Null("column".to_owned());
let expected = "error occurred while decoding column: Null".to_owned();
assert_eq!(DbErr::from(try_get_error), DbErr::Query(expected));
let expected = "A null value was encountered while decoding column".to_owned();
assert_eq!(DbErr::from(try_get_error), DbErr::Type(expected));
}
}

View File

@ -116,7 +116,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::UpdateGetPrimeryKey),
};
let found = <A::Entity as EntityTrait>::find_by_id(primary_key_value)
.one(db)
@ -124,7 +124,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 updated item".to_owned(),
)),
}
}
}

56
tests/crud/error.rs Normal file
View File

@ -0,0 +1,56 @@
pub use super::*;
use rust_decimal_macros::dec;
use sea_orm::error::*;
#[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::Exec(RuntimeErr::SqlxError(error)) => match 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::Query(RuntimeErr::SqlxError(error)) => match error {
Error::Database(e) => {
assert_eq!(e.code().unwrap(), "23505");
}
_ => panic!("Unexpected sqlx-error kind"),
},
_ => panic!("Unexpected Error kind"),
}
}

View File

@ -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::*;

View File

@ -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;
}

View File

@ -508,7 +508,9 @@ pub async fn transaction_nested() {
assert_eq!(bakeries.len(), 4);
if true {
Err(DbErr::Query("Force Rollback!".to_owned()))
Err(DbErr::Query(RuntimeErr::Internal(
"Force Rollback!".to_owned(),
)))
} else {
Ok(())
}
@ -633,7 +635,9 @@ pub async fn transaction_nested() {
assert_eq!(bakeries.len(), 7);
if true {
Err(DbErr::Query("Force Rollback!".to_owned()))
Err(DbErr::Query(RuntimeErr::Internal(
"Force Rollback!".to_owned(),
)))
} else {
Ok(())
}
@ -652,7 +656,9 @@ pub async fn transaction_nested() {
assert_eq!(bakeries.len(), 6);
if true {
Err(DbErr::Query("Force Rollback!".to_owned()))
Err(DbErr::Query(RuntimeErr::Internal(
"Force Rollback!".to_owned(),
)))
} else {
Ok(())
}