commit
5d752e60b9
@ -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" }
|
||||
|
@ -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)))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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>)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)))
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
85
src/error.rs
85
src/error.rs
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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
56
tests/crud/error.rs
Normal 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"),
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user