use std::cell::RefCell;
use std::path::Path;
use std::sync::Arc;
use parking_lot::ReentrantMutex;
use crate::application::command_handlers::PurgeCommandHandler;
use crate::application::traits::RepositoryProvider;
use crate::domain::environment::repository::EnvironmentRepository;
use crate::infrastructure::persistence::file_repository_factory::FileRepositoryFactory;
use crate::presentation::cli::controllers::configure::ConfigureCommandController;
use crate::presentation::cli::controllers::constants::DEFAULT_LOCK_TIMEOUT;
use crate::presentation::cli::controllers::create::subcommands::environment::CreateEnvironmentCommandController;
use crate::presentation::cli::controllers::create::subcommands::schema::CreateSchemaCommandController;
use crate::presentation::cli::controllers::create::subcommands::template::CreateTemplateCommandController;
use crate::presentation::cli::controllers::destroy::DestroyCommandController;
use crate::presentation::cli::controllers::docs::DocsCommandController;
use crate::presentation::cli::controllers::exists::ExistsCommandController;
use crate::presentation::cli::controllers::list::ListCommandController;
use crate::presentation::cli::controllers::provision::ProvisionCommandController;
use crate::presentation::cli::controllers::purge::PurgeCommandController;
use crate::presentation::cli::controllers::register::RegisterCommandController;
use crate::presentation::cli::controllers::release::ReleaseCommandController;
use crate::presentation::cli::controllers::render::RenderCommandController;
use crate::presentation::cli::controllers::run::RunCommandController;
use crate::presentation::cli::controllers::show::ShowCommandController;
use crate::presentation::cli::controllers::test::handler::TestCommandController;
use crate::presentation::cli::controllers::validate::ValidateCommandController;
use crate::presentation::cli::views::{UserOutput, VerbosityLevel};
use crate::shared::clock::Clock;
use crate::shared::SystemClock;
#[derive(Clone)]
pub struct Container {
user_output: Arc<ReentrantMutex<RefCell<UserOutput>>>,
file_repository_factory: Arc<FileRepositoryFactory>,
repository: Arc<dyn EnvironmentRepository + Send + Sync>,
clock: Arc<dyn Clock>,
data_directory: Arc<Path>,
}
impl Container {
#[must_use]
pub fn new(verbosity_level: VerbosityLevel, working_dir: &Path) -> Self {
let user_output = Arc::new(ReentrantMutex::new(RefCell::new(UserOutput::new(
verbosity_level,
))));
let file_repository_factory = Arc::new(FileRepositoryFactory::new(DEFAULT_LOCK_TIMEOUT));
let data_dir = working_dir.join("data");
let data_directory: Arc<Path> = Arc::from(data_dir.as_path());
let repository = file_repository_factory.create(data_dir);
let clock: Arc<dyn Clock> = Arc::new(SystemClock);
Self {
user_output,
file_repository_factory,
repository,
clock,
data_directory,
}
}
#[must_use]
pub fn user_output(&self) -> Arc<ReentrantMutex<RefCell<UserOutput>>> {
Arc::clone(&self.user_output)
}
#[must_use]
pub fn file_repository_factory(&self) -> Arc<FileRepositoryFactory> {
Arc::clone(&self.file_repository_factory)
}
#[must_use]
pub fn repository_provider(&self) -> Arc<dyn RepositoryProvider> {
Arc::clone(&self.file_repository_factory) as Arc<dyn RepositoryProvider>
}
#[must_use]
pub fn repository(&self) -> Arc<dyn EnvironmentRepository + Send + Sync> {
Arc::clone(&self.repository)
}
#[must_use]
pub fn clock(&self) -> Arc<dyn Clock> {
Arc::clone(&self.clock)
}
#[must_use]
pub fn create_environment_controller(&self) -> CreateEnvironmentCommandController {
CreateEnvironmentCommandController::new(
self.repository(),
self.clock(),
&self.user_output(),
)
}
#[must_use]
pub fn create_template_controller(&self) -> CreateTemplateCommandController {
CreateTemplateCommandController::new(&self.user_output())
}
#[must_use]
pub fn create_schema_controller(&self) -> CreateSchemaCommandController {
CreateSchemaCommandController::new(&self.user_output())
}
#[must_use]
pub fn create_docs_controller(&self) -> DocsCommandController {
DocsCommandController::new(&self.user_output())
}
#[must_use]
pub fn create_provision_controller(&self) -> ProvisionCommandController {
ProvisionCommandController::new(self.repository(), self.clock(), self.user_output())
}
#[must_use]
pub fn create_destroy_controller(&self) -> DestroyCommandController {
DestroyCommandController::new(self.repository(), self.clock(), self.user_output())
}
#[must_use]
pub fn create_purge_controller(&self) -> PurgeCommandController {
let handler =
PurgeCommandHandler::new(self.repository(), (*self.data_directory).to_path_buf());
PurgeCommandController::new(handler, self.user_output())
}
#[must_use]
pub fn create_configure_controller(&self) -> ConfigureCommandController {
ConfigureCommandController::new(self.repository(), self.clock(), self.user_output())
}
#[must_use]
pub fn create_test_controller(&self) -> TestCommandController {
TestCommandController::new(self.repository(), self.user_output())
}
#[must_use]
pub fn create_validate_controller(&self) -> ValidateCommandController {
ValidateCommandController::new(self.user_output())
}
#[must_use]
pub fn create_register_controller(&self) -> RegisterCommandController {
RegisterCommandController::new(self.repository(), self.clock(), self.user_output())
}
#[must_use]
pub fn create_release_controller(&self) -> ReleaseCommandController {
ReleaseCommandController::new(self.repository(), self.clock(), self.user_output())
}
#[must_use]
pub fn create_render_controller(&self) -> RenderCommandController {
RenderCommandController::new(self.repository(), self.user_output())
}
#[must_use]
pub fn create_run_controller(&self) -> RunCommandController {
RunCommandController::new(self.repository(), self.clock(), self.user_output())
}
#[must_use]
pub fn create_show_controller(&self) -> ShowCommandController {
ShowCommandController::new(self.repository(), self.user_output())
}
#[must_use]
pub fn create_exists_controller(&self) -> ExistsCommandController {
ExistsCommandController::new(self.repository(), self.user_output())
}
#[must_use]
pub fn create_list_controller(&self) -> ListCommandController {
ListCommandController::new(
self.repository_provider(),
self.data_directory(),
self.user_output(),
)
}
#[must_use]
pub fn data_directory(&self) -> Arc<Path> {
Arc::clone(&self.data_directory)
}
}
impl Default for Container {
fn default() -> Self {
Self::new(VerbosityLevel::Normal, Path::new("."))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn it_should_create_container_with_all_services() {
let temp_dir = TempDir::new().unwrap();
let container = Container::new(VerbosityLevel::Normal, temp_dir.path());
let user_output = container.user_output();
let file_repository_factory = container.file_repository_factory();
let repository = container.repository();
let clock = container.clock();
assert!(Arc::strong_count(&user_output) >= 1);
assert!(Arc::strong_count(&file_repository_factory) >= 1);
assert!(Arc::strong_count(&repository) >= 1);
assert!(Arc::strong_count(&clock) >= 1);
}
#[test]
fn it_should_return_cloned_arc_on_file_repository_factory_access() {
let temp_dir = TempDir::new().unwrap();
let container = Container::new(VerbosityLevel::Normal, temp_dir.path());
let factory1 = container.file_repository_factory();
let factory2 = container.file_repository_factory();
assert!(Arc::ptr_eq(&factory1, &factory2));
}
#[test]
fn it_should_return_cloned_arc_on_repository_access() {
let temp_dir = TempDir::new().unwrap();
let container = Container::new(VerbosityLevel::Normal, temp_dir.path());
let repo1 = container.repository();
let repo2 = container.repository();
assert!(Arc::ptr_eq(&repo1, &repo2));
}
#[test]
fn it_should_return_cloned_arc_on_clock_access() {
let temp_dir = TempDir::new().unwrap();
let container = Container::new(VerbosityLevel::Normal, temp_dir.path());
let clock1 = container.clock();
let clock2 = container.clock();
assert!(Arc::ptr_eq(&clock1, &clock2));
}
#[test]
fn it_should_return_cloned_arc_on_user_output_access() {
let temp_dir = TempDir::new().unwrap();
let container = Container::new(VerbosityLevel::Normal, temp_dir.path());
let user_output1 = container.user_output();
let user_output2 = container.user_output();
assert!(Arc::ptr_eq(&user_output1, &user_output2));
}
#[test]
fn it_should_be_clonable() {
let temp_dir = TempDir::new().unwrap();
let container1 = Container::new(VerbosityLevel::Normal, temp_dir.path());
let container2 = container1.clone();
let user_output1 = container1.user_output();
let user_output2 = container2.user_output();
assert!(Arc::ptr_eq(&user_output1, &user_output2));
let factory1 = container1.file_repository_factory();
let factory2 = container2.file_repository_factory();
assert!(Arc::ptr_eq(&factory1, &factory2));
let repo1 = container1.repository();
let repo2 = container2.repository();
assert!(Arc::ptr_eq(&repo1, &repo2));
let clock1 = container1.clock();
let clock2 = container2.clock();
assert!(Arc::ptr_eq(&clock1, &clock2));
}
#[test]
fn it_should_create_container_with_silent_verbosity_for_tests() {
let temp_dir = TempDir::new().unwrap();
let container = Container::new(VerbosityLevel::Silent, temp_dir.path());
let user_output = container.user_output();
let file_repository_factory = container.file_repository_factory();
let repository = container.repository();
let clock = container.clock();
assert!(Arc::strong_count(&user_output) >= 1);
assert!(Arc::strong_count(&file_repository_factory) >= 1);
assert!(Arc::strong_count(&repository) >= 1);
assert!(Arc::strong_count(&clock) >= 1);
}
#[test]
fn it_should_create_container_with_different_verbosity_levels() {
let temp_dir = TempDir::new().unwrap();
let levels = [
VerbosityLevel::Silent,
VerbosityLevel::Quiet,
VerbosityLevel::Normal,
VerbosityLevel::Verbose,
VerbosityLevel::VeryVerbose,
VerbosityLevel::Debug,
];
for level in &levels {
let container = Container::new(*level, temp_dir.path());
let user_output = container.user_output();
let file_repository_factory = container.file_repository_factory();
let repository = container.repository();
let clock = container.clock();
assert!(Arc::strong_count(&user_output) >= 1);
assert!(Arc::strong_count(&file_repository_factory) >= 1);
assert!(Arc::strong_count(&repository) >= 1);
assert!(Arc::strong_count(&clock) >= 1);
}
}
}