use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, OnceLock};
use trait_kit::prelude::*;
use oxcache::integrations::kit::OxcacheModule;
use crate::database::{ConnectionPool, DbPoolBuilder};
use crate::foundation::DbConfig;
use crate::foundation::DbError;
use crate::integrations::OxcacheDbCacheAdapter;
pub struct DbNexusModule;
impl ModuleMeta for DbNexusModule {
const NAME: &'static str = "dbnexus";
fn dependencies() -> &'static [(&'static str, TypeId)] {
static DEPS: OnceLock<Vec<(&'static str, TypeId)>> = OnceLock::new();
DEPS.get_or_init(|| vec![("oxcache", TypeId::of::<OxcacheModule>())])
.as_slice()
}
}
impl AsyncAutoBuilder for DbNexusModule {
type Capability = Arc<dyn ConnectionPool + Send + Sync>;
type Error = DbError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move {
let cache_cap = kit
.require::<OxcacheModule>()
.map_err(|e| DbError::Config(format!("require OxcacheModule: {e}")))?;
let _ = OxcacheDbCacheAdapter::new(cache_cap);
let config: DbConfig = kit
.config()
.map_err(|e| DbError::Config(format!("read DbConfig: {e}")))?;
let pool = DbPoolBuilder::new().config(config).build().await?;
Ok(Arc::new(pool) as Arc<dyn ConnectionPool + Send + Sync>)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxcache::integrations::kit::OxcacheConfig;
#[test]
fn dbnexus_module_meta_name() {
assert_eq!(DbNexusModule::NAME, "dbnexus");
}
#[test]
fn dbnexus_module_meta_dependencies() {
let deps = DbNexusModule::dependencies();
assert_eq!(deps.len(), 1, "DbNexusModule should depend on 1 module");
assert_eq!(deps[0].0, "oxcache", "dep name should be 'oxcache'");
assert_eq!(
deps[0].1,
TypeId::of::<OxcacheModule>(),
"dep TypeId should match OxcacheModule"
);
}
#[test]
fn dbnexus_module_satisfies_async_auto_builder_bounds() {
fn assert_cap<T: Clone + Send + Sync + 'static>() {}
assert_cap::<Arc<dyn ConnectionPool + Send + Sync>>();
fn assert_err<T: std::error::Error + Send + 'static>() {}
assert_err::<DbError>();
}
#[tokio::test]
async fn dbnexus_module_build_returns_connection_pool() {
let mut kit = AsyncKit::new();
kit.set_config(OxcacheConfig::default());
kit.set_config(DbConfig {
url: "sqlite::memory:".to_string(),
max_connections: 5,
min_connections: 1,
..Default::default()
});
kit.register::<OxcacheModule>().expect("register OxcacheModule");
kit.register::<DbNexusModule>().expect("register DbNexusModule");
let kit = kit.build().await.expect("AsyncKit::build");
let pool: Arc<dyn ConnectionPool + Send + Sync> =
kit.require::<DbNexusModule>().expect("require DbNexusModule");
let _status = pool.status();
let config = pool.config();
assert_eq!(config.url, "sqlite::memory:");
}
#[tokio::test]
async fn dbnexus_module_build_fails_without_oxcache() {
let mut kit = AsyncKit::new();
kit.set_config(DbConfig {
url: "sqlite::memory:".to_string(),
..Default::default()
});
kit.register::<DbNexusModule>().expect("register DbNexusModule");
let err = kit.build().await.expect_err("build should fail");
let msg = err.to_string();
assert!(
msg.contains("oxcache"),
"error should mention oxcache dependency, got: {msg}"
);
}
}