use std::sync::Arc;
use crate::domain::core::manager::{LoggerDependencies, LoggerManager};
#[cfg(test)]
use crate::integrations::infra::cache::MockCache;
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
use crate::integrations::infra::Database;
use crate::integrations::infra::{Cache, Config, InklogConfigAdapter, OxCacheAdapter};
use crate::InklogConfig;
use crate::InklogError;
pub struct InklogContainer {
cache: Arc<dyn Cache>,
config: Arc<dyn Config>,
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
database: Option<Arc<dyn Database>>,
}
impl InklogContainer {
pub fn new() -> Result<Self, InklogError> {
let cache = Arc::new(OxCacheAdapter::new()?);
let config = Arc::new(InklogConfigAdapter::new()?);
Ok(Self {
cache,
config,
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
database: None,
})
}
pub fn from_config(config: InklogConfig) -> Result<Self, InklogError> {
let cache = Arc::new(OxCacheAdapter::new()?);
let config = Arc::new(InklogConfigAdapter::from_config(config));
Ok(Self {
cache,
config,
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
database: None,
})
}
pub fn builder() -> InklogContainerBuilder {
InklogContainerBuilder::default()
}
pub async fn create_logger(&self) -> Result<LoggerManager, InklogError> {
let deps = LoggerDependencies {
cache: Some(Arc::clone(&self.cache)),
config: Some(Arc::clone(&self.config)),
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
database: self.database.clone(),
};
LoggerManager::with_dependencies(deps).await
}
pub fn cache(&self) -> Arc<dyn Cache> {
Arc::clone(&self.cache)
}
pub fn config(&self) -> Arc<dyn Config> {
Arc::clone(&self.config)
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
pub fn database(&self) -> Option<Arc<dyn Database>> {
self.database.clone()
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
pub fn set_database(&mut self, database: Arc<dyn Database>) {
self.database = Some(database);
}
}
impl Default for InklogContainer {
fn default() -> Self {
Self::new().expect("Failed to create default InklogContainer")
}
}
impl std::fmt::Debug for InklogContainer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut builder = f.debug_struct("InklogContainer");
builder
.field("cache", &"Arc<dyn Cache>")
.field("config", &"Arc<dyn Config>");
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
builder.field(
"database",
&self.database.as_ref().map(|_| "Arc<dyn Database>"),
);
builder.finish()
}
}
#[derive(Default)]
pub struct InklogContainerBuilder {
cache: Option<Arc<dyn Cache>>,
config: Option<Arc<dyn Config>>,
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
database: Option<Arc<dyn Database>>,
}
impl InklogContainerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn cache(mut self, cache: Arc<dyn Cache>) -> Self {
self.cache = Some(cache);
self
}
pub fn config(mut self, config: Arc<dyn Config>) -> Self {
self.config = Some(config);
self
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
pub fn database(mut self, database: Arc<dyn Database>) -> Self {
self.database = Some(database);
self
}
pub fn build(self) -> Result<InklogContainer, InklogError> {
let cache = self.cache.unwrap_or_else(|| {
Arc::new(OxCacheAdapter::new().expect("Failed to create default cache"))
});
let config = self.config.unwrap_or_else(|| {
Arc::new(InklogConfigAdapter::new().expect("Failed to create default config"))
});
Ok(InklogContainer {
cache,
config,
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
database: self.database,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::integrations::infra::{InklogConfigAdapter, OxCacheAdapter};
use serial_test::serial;
#[test]
fn test_container_new() {
let container = InklogContainer::new();
assert!(container.is_ok());
}
#[test]
fn test_container_default() {
let container = InklogContainer::default();
let _cache = container.cache();
let _config = container.config();
}
#[test]
fn test_container_from_config() {
let config = InklogConfig::default();
let container = InklogContainer::from_config(config);
assert!(container.is_ok());
}
#[test]
fn test_container_builder_default() {
let container = InklogContainer::builder().build();
assert!(container.is_ok());
}
#[test]
fn test_container_builder_with_adapters() {
let container = InklogContainer::builder()
.cache(Arc::new(
OxCacheAdapter::new().expect("Failed to create cache"),
))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.build();
assert!(container.is_ok());
}
#[tokio::test]
async fn test_container_cache_shared() {
let container = InklogContainer::builder()
.cache(Arc::new(
OxCacheAdapter::new().expect("Failed to create cache"),
))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.build()
.unwrap();
let cache1 = container.cache();
let cache2 = container.cache();
cache1.set("key", "value".to_string()).await.unwrap();
let value = cache2.get("key").await.unwrap();
assert_eq!(value, Some("value".to_string()));
}
#[test]
fn test_container_config_shared() {
let config = InklogConfig::default();
let adapter = InklogConfigAdapter::from_config(config);
let container = InklogContainer::builder()
.cache(Arc::new(
OxCacheAdapter::new().expect("Failed to create cache"),
))
.config(Arc::new(adapter))
.build()
.unwrap();
let config1 = container.config();
let config2 = container.config();
assert_eq!(
config1.get_string("global.level"),
config2.get_string("global.level")
);
}
#[tokio::test(flavor = "multi_thread")]
#[serial]
async fn test_container_create_logger() {
let container = InklogContainer::builder()
.cache(Arc::new(
OxCacheAdapter::new().expect("Failed to create cache"),
))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.build()
.unwrap();
let result = container.create_logger().await;
assert!(result.is_ok());
}
#[test]
fn test_container_debug() {
let container = InklogContainer::builder()
.cache(Arc::new(
OxCacheAdapter::new().expect("Failed to create cache"),
))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.build()
.unwrap();
let debug_str = format!("{:?}", container);
assert!(debug_str.contains("InklogContainer"));
assert!(debug_str.contains("Arc<dyn Cache>"));
assert!(debug_str.contains("Arc<dyn Config>"));
}
#[test]
fn test_container_with_mock_cache() {
let container = InklogContainer::builder()
.cache(Arc::new(MockCache::new()))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.build();
assert!(container.is_ok());
}
#[tokio::test]
async fn test_container_mock_cache_operations() {
let container = InklogContainer::builder()
.cache(Arc::new(MockCache::new()))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.build()
.unwrap();
let cache = container.cache();
cache
.set("test_key", "test_value".to_string())
.await
.unwrap();
let value = cache.get("test_key").await.unwrap();
assert_eq!(value, Some("test_value".to_string()));
assert!(cache.exists("test_key").await.unwrap());
assert!(cache.delete("test_key").await.unwrap());
assert!(!cache.exists("test_key").await.unwrap());
}
#[test]
fn test_container_builder_new() {
let builder = InklogContainerBuilder::new();
let container = builder.build();
assert!(container.is_ok());
}
#[test]
fn test_container_builder_new_equals_default() {
let from_new = InklogContainerBuilder::new();
let from_default = InklogContainerBuilder::default();
assert!(from_new.build().is_ok());
assert!(from_default.build().is_ok());
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
#[tokio::test]
async fn test_container_database_returns_none_by_default() {
let container = InklogContainer::builder()
.cache(Arc::new(MockCache::new()))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.build()
.unwrap();
assert!(container.database().is_none());
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
#[tokio::test]
async fn test_container_set_database_and_get() {
use crate::integrations::infra::MockDatabaseAdapter;
use crate::LogRecord;
let mut container = InklogContainer::builder()
.cache(Arc::new(MockCache::new()))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.build()
.unwrap();
assert!(container.database().is_none());
let mock_db = Arc::new(MockDatabaseAdapter::new());
let db: Arc<dyn Database> = Arc::clone(&mock_db) as Arc<dyn Database>;
container.set_database(Arc::clone(&db));
let retrieved = container.database();
assert!(retrieved.is_some());
let retrieved_db = retrieved.unwrap();
assert!(retrieved_db.is_healthy().await);
let records = vec![LogRecord::new(
tracing::Level::INFO,
"test::module".to_string(),
"test message".to_string(),
)];
let count = retrieved_db.insert_batch(&records).await.unwrap();
assert_eq!(count, 1);
assert_eq!(mock_db.record_count(), 1);
}
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
#[tokio::test]
async fn test_container_builder_database() {
use crate::integrations::infra::MockDatabaseAdapter;
let mock_db = Arc::new(MockDatabaseAdapter::new());
let db: Arc<dyn Database> = Arc::clone(&mock_db) as Arc<dyn Database>;
let container = InklogContainer::builder()
.cache(Arc::new(MockCache::new()))
.config(Arc::new(InklogConfigAdapter::from_config(
InklogConfig::default(),
)))
.database(Arc::clone(&db))
.build()
.unwrap();
let retrieved = container.database();
assert!(retrieved.is_some());
let retrieved_db = retrieved.unwrap();
assert!(retrieved_db.is_healthy().await);
assert_eq!(mock_db.record_count(), 0);
}
}