Skip to main content

wrale_acdm/application/
use_cases.rs

1// Copyright (c) 2025 Wrale LTD <contact@wrale.com>
2
3use anyhow::{Context, Result};
4use std::path::{Path, PathBuf};
5
6use crate::application::dto::{
7    AddDependencyDto, IncludePathsDto, InitConfigDto, UpdateDependenciesDto,
8};
9use crate::domain::repositories::{
10    ConfigurationRepository, FileSystemManager, GitOperations, RepositoryFetcher,
11};
12use crate::domain::services::DependencyManager;
13use crate::domain::{Dependency, RepositoryType};
14
15/// Use case for initializing a new configuration
16pub struct InitConfigUseCase<C: ConfigurationRepository> {
17    config_repo: C,
18}
19
20impl<C: ConfigurationRepository> InitConfigUseCase<C> {
21    pub fn new(config_repo: C) -> Self {
22        Self { config_repo }
23    }
24
25    pub fn execute(&self, dto: InitConfigDto) -> Result<()> {
26        // Initialize the configuration
27        self.config_repo
28            .init(&dto.config_path)
29            .context("Failed to initialize configuration")?;
30
31        // If a default location was provided, update the config
32        if let Some(location) = dto.default_location {
33            let mut config = self
34                .config_repo
35                .load(&dto.config_path)
36                .context("Failed to load configuration")?;
37
38            config.default_location = Some(PathBuf::from(location));
39
40            self.config_repo
41                .save(&config, &dto.config_path)
42                .context("Failed to save configuration")?;
43        }
44
45        Ok(())
46    }
47}
48
49/// Use case for adding a new dependency
50pub struct AddDependencyUseCase<C: ConfigurationRepository> {
51    config_repo: C,
52}
53
54impl<C: ConfigurationRepository> AddDependencyUseCase<C> {
55    pub fn new(config_repo: C) -> Self {
56        Self { config_repo }
57    }
58
59    pub fn execute(&self, config_path: &Path, dto: AddDependencyDto) -> Result<()> {
60        // Load the configuration
61        let mut config = self
62            .config_repo
63            .load(config_path)
64            .context("Failed to load configuration")?;
65
66        // Check if a dependency with the same name already exists
67        if config.dependencies.iter().any(|d| d.name == dto.name) {
68            return Err(anyhow::anyhow!(
69                "A dependency with the name '{}' already exists",
70                dto.name
71            ));
72        }
73
74        // Parse the repository type
75        let repo_type = match dto.repository_type.to_lowercase().as_str() {
76            "git" => RepositoryType::Git,
77            _ => {
78                return Err(anyhow::anyhow!(
79                    "Unsupported repository type: {}",
80                    dto.repository_type
81                ))
82            }
83        };
84
85        // Create a new dependency
86        let dependency = Dependency {
87            name: dto.name,
88            repository_url: dto.repository_url,
89            revision: dto.revision,
90            repository_type: repo_type,
91            sparse_paths: Vec::new(),
92            target_location: PathBuf::from(dto.target_location),
93        };
94
95        // Add the dependency to the configuration
96        config.dependencies.push(dependency);
97
98        // Save the configuration
99        self.config_repo
100            .save(&config, config_path)
101            .context("Failed to save configuration")?;
102
103        Ok(())
104    }
105}
106
107/// Use case for including paths in a dependency
108pub struct IncludePathsUseCase<C: ConfigurationRepository> {
109    config_repo: C,
110}
111
112impl<C: ConfigurationRepository> IncludePathsUseCase<C> {
113    pub fn new(config_repo: C) -> Self {
114        Self { config_repo }
115    }
116
117    pub fn execute(&self, config_path: &Path, dto: IncludePathsDto) -> Result<()> {
118        // Load the configuration
119        let mut config = self
120            .config_repo
121            .load(config_path)
122            .context("Failed to load configuration")?;
123
124        // Find the dependency by name
125        let dep_idx = config
126            .dependencies
127            .iter()
128            .position(|d| d.name == dto.dependency_name)
129            .ok_or_else(|| anyhow::anyhow!("Dependency '{}' not found", dto.dependency_name))?;
130
131        // Add the paths to the dependency
132        for path in dto.paths {
133            if !config.dependencies[dep_idx].sparse_paths.contains(&path) {
134                config.dependencies[dep_idx].sparse_paths.push(path);
135            }
136        }
137
138        // Save the configuration
139        self.config_repo
140            .save(&config, config_path)
141            .context("Failed to save configuration")?;
142
143        Ok(())
144    }
145}
146
147/// Use case for updating dependencies
148pub struct UpdateDependenciesUseCase<C, R, F, G>
149where
150    C: ConfigurationRepository,
151    R: RepositoryFetcher,
152    F: FileSystemManager,
153    G: GitOperations,
154{
155    config_repo: C,
156    dependency_manager: DependencyManager<R, F, G>,
157}
158
159impl<C, R, F, G> UpdateDependenciesUseCase<C, R, F, G>
160where
161    C: ConfigurationRepository,
162    R: RepositoryFetcher,
163    F: FileSystemManager,
164    G: GitOperations,
165{
166    pub fn new(
167        config_repo: C,
168        repository_fetcher: R,
169        file_system_manager: F,
170        git_operations: G,
171    ) -> Self {
172        Self {
173            config_repo,
174            dependency_manager: DependencyManager::new(
175                repository_fetcher,
176                file_system_manager,
177                git_operations,
178            ),
179        }
180    }
181
182    pub fn execute(&self, dto: UpdateDependenciesDto) -> Result<()> {
183        // Load the configuration
184        let config = self
185            .config_repo
186            .load(&dto.config_path)
187            .context("Failed to load configuration")?;
188
189        // Get the dependencies to update
190        let dependencies_to_update = if let Some(dep_names) = dto.dependencies {
191            config
192                .dependencies
193                .iter()
194                .filter(|d| dep_names.contains(&d.name))
195                .cloned()
196                .collect::<Vec<_>>()
197        } else {
198            config.dependencies.clone()
199        };
200
201        if dependencies_to_update.is_empty() {
202            return Err(anyhow::anyhow!("No dependencies found to update"));
203        }
204
205        // Get the repository root (the directory containing the config file)
206        let repo_root = dto
207            .config_path
208            .parent()
209            .ok_or_else(|| anyhow::anyhow!("Failed to determine repository root"))?;
210
211        // Update all dependencies
212        self.dependency_manager
213            .update_all(&dependencies_to_update, repo_root, dto.force)
214            .map_err(|e| anyhow::anyhow!("Failed to update dependencies: {}", e))?;
215
216        Ok(())
217    }
218}