use rustrails_support::runtime;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
#[derive(Debug, thiserror::Error)]
pub enum ConnectionError {
#[error("connection failed: {0}")]
ConnectionFailed(String),
#[error("not connected")]
NotConnected,
}
#[derive(Clone, Debug)]
pub struct ConnectionPool {
db: DatabaseConnection,
}
impl ConnectionPool {
pub async fn connect(url: &str) -> Result<Self, ConnectionError> {
let db = Database::connect(url)
.await
.map_err(|error| ConnectionError::ConnectionFailed(error.to_string()))?;
Ok(Self { db })
}
pub fn connect_sync(url: &str) -> Result<Self, ConnectionError> {
runtime::block_on(Self::connect(url))
}
pub async fn connect_with_options(options: ConnectOptions) -> Result<Self, ConnectionError> {
let db = Database::connect(options)
.await
.map_err(|error| ConnectionError::ConnectionFailed(error.to_string()))?;
Ok(Self { db })
}
pub fn connect_with_options_sync(options: ConnectOptions) -> Result<Self, ConnectionError> {
runtime::block_on(Self::connect_with_options(options))
}
pub fn connection(&self) -> &DatabaseConnection {
&self.db
}
pub async fn ping(&self) -> Result<(), ConnectionError> {
self.db
.ping()
.await
.map_err(|error| ConnectionError::ConnectionFailed(error.to_string()))
}
pub fn ping_sync(&self) -> Result<(), ConnectionError> {
runtime::block_on(self.ping())
}
pub async fn close(self) -> Result<(), ConnectionError> {
self.db
.close()
.await
.map_err(|error| ConnectionError::ConnectionFailed(error.to_string()))
}
pub fn close_sync(self) -> Result<(), ConnectionError> {
runtime::block_on(self.close())
}
}
pub async fn establish(url: &str) -> Result<ConnectionPool, ConnectionError> {
ConnectionPool::connect(url).await
}
pub fn establish_sync(url: &str) -> Result<ConnectionPool, ConnectionError> {
runtime::block_on(establish(url))
}
#[cfg(test)]
mod tests {
use rustrails_support::{database, runtime};
use sea_orm::{ConnectOptions, DatabaseBackend};
use super::{ConnectionError, ConnectionPool, establish, establish_sync};
fn run_sync_connection_test(test: impl FnOnce() + Send + 'static) {
std::thread::spawn(move || {
let _rt = runtime::init_runtime();
database::establish("sqlite::memory:")
.expect("sqlite in-memory connection should succeed");
test();
})
.join()
.unwrap();
}
#[tokio::test]
async fn connect_to_in_memory_sqlite() {
let pool = ConnectionPool::connect("sqlite::memory:")
.await
.expect("sqlite in-memory connection should succeed");
assert_eq!(
pool.connection().get_database_backend(),
DatabaseBackend::Sqlite
);
}
#[tokio::test]
async fn connect_with_options_uses_same_backend() {
let mut options = ConnectOptions::new("sqlite::memory:");
options.sqlx_logging(false);
let pool = ConnectionPool::connect_with_options(options)
.await
.expect("sqlite in-memory connection should succeed");
assert_eq!(
pool.connection().get_database_backend(),
DatabaseBackend::Sqlite
);
}
#[tokio::test]
async fn ping_succeeds_for_live_connection() {
let pool = establish("sqlite::memory:")
.await
.expect("sqlite in-memory connection should succeed");
pool.ping().await.expect("ping should succeed");
}
#[tokio::test]
async fn close_succeeds_for_live_connection() {
let pool = establish("sqlite::memory:")
.await
.expect("sqlite in-memory connection should succeed");
pool.close().await.expect("close should succeed");
}
#[test]
fn connect_sync_to_in_memory_sqlite() {
run_sync_connection_test(|| {
let pool = ConnectionPool::connect_sync("sqlite::memory:")
.expect("sqlite in-memory connection should succeed");
assert_eq!(
pool.connection().get_database_backend(),
DatabaseBackend::Sqlite
);
});
}
#[test]
fn connect_with_options_sync_uses_same_backend() {
run_sync_connection_test(|| {
let mut options = ConnectOptions::new("sqlite::memory:");
options.sqlx_logging(false);
let pool = ConnectionPool::connect_with_options_sync(options)
.expect("sqlite in-memory connection should succeed");
assert_eq!(
pool.connection().get_database_backend(),
DatabaseBackend::Sqlite
);
});
}
#[test]
fn ping_sync_succeeds_for_live_connection() {
run_sync_connection_test(|| {
let pool = establish_sync("sqlite::memory:")
.expect("sqlite in-memory connection should succeed");
pool.ping_sync().expect("ping should succeed");
});
}
#[test]
fn close_sync_succeeds_for_live_connection() {
run_sync_connection_test(|| {
let pool = establish_sync("sqlite::memory:")
.expect("sqlite in-memory connection should succeed");
pool.close_sync().expect("close should succeed");
});
}
#[test]
fn establish_sync_creates_connection_pool() {
run_sync_connection_test(|| {
let pool = establish_sync("sqlite::memory:")
.expect("sqlite in-memory connection should succeed");
assert_eq!(
pool.connection().get_database_backend(),
DatabaseBackend::Sqlite
);
});
}
#[tokio::test]
async fn invalid_url_returns_connection_error() {
let result = ConnectionPool::connect("not-a-valid-database-url").await;
assert!(matches!(result, Err(ConnectionError::ConnectionFailed(_))));
}
}