use crate::config::DatabaseConfig;
use crate::error::{ServiceError, ServiceResult};
use sqlx::postgres::PgPoolOptions;
#[derive(Debug, Clone)]
pub struct Database {
pool: sqlx::PgPool,
}
impl Database {
pub(crate) fn from_pool(pool: sqlx::PgPool) -> Self {
Self { pool }
}
pub fn from_config(config: &DatabaseConfig) -> ServiceResult<Self> {
super::connect(config)
}
pub fn pool(&self) -> &sqlx::PgPool {
&self.pool
}
pub async fn ping(&self) -> ServiceResult<()> {
let result = tokio::time::timeout(
std::time::Duration::from_secs(2),
sqlx::query("SELECT 1").execute(&self.pool),
)
.await;
match result {
Ok(Ok(_)) => Ok(()),
Ok(Err(e)) => Err(ServiceError::Database(e.to_string())),
Err(_) => Err(ServiceError::DeadlineExceeded(
"database ping timed out after 2s".to_string(),
)),
}
}
}
pub(super) fn build_pool_lazy(config: &DatabaseConfig) -> ServiceResult<sqlx::PgPool> {
let pool = PgPoolOptions::new()
.max_connections(config.max_connections)
.min_connections(0)
.acquire_timeout(std::time::Duration::from_secs(config.connect_timeout))
.connect_lazy(&config.url)
.map_err(|e| ServiceError::Database(e.to_string()))?;
Ok(pool)
}
pub(super) async fn build_pool_eager(config: &DatabaseConfig) -> ServiceResult<sqlx::PgPool> {
let pool = PgPoolOptions::new()
.max_connections(config.max_connections)
.min_connections(0)
.acquire_timeout(std::time::Duration::from_secs(config.connect_timeout))
.connect(&config.url)
.await
.map_err(|e| ServiceError::Database(e.to_string()))?;
Ok(pool)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_build_pool_lazy_rejects_bad_url() {
let config = DatabaseConfig {
url: "not-a-valid-postgres-url".to_string(),
max_connections: 5,
connect_timeout: 5,
};
let result = build_pool_lazy(&config);
assert!(result.is_err(), "expected error for malformed URL, got Ok");
match result.unwrap_err() {
ServiceError::Database(_) => {}
other => panic!("expected Database variant, got {other:?}"),
}
}
#[tokio::test]
async fn test_from_config_delegates_to_connect() {
let config = DatabaseConfig {
url: "postgres://user:pass@localhost:9999/nonexistent".to_string(),
max_connections: 2,
connect_timeout: 2,
};
let result = Database::from_config(&config);
assert!(result.is_ok(), "connect_lazy should not fail for valid URL");
}
}