adding several features
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::RwLock;
|
||||
use crate::common::errors::{DomainError, ErrorKind};
|
||||
|
||||
/// Entrada de caché con tiempo de expiración
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct CacheEntry<V> {
|
||||
value: V,
|
||||
expiry: Instant,
|
||||
}
|
||||
|
||||
impl<V> CacheEntry<V> {
|
||||
/// Crea una nueva entrada en la caché
|
||||
#[allow(dead_code)]
|
||||
fn new(value: V, ttl: Duration) -> Self {
|
||||
Self {
|
||||
value,
|
||||
expiry: Instant::now() + ttl,
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifica si la entrada ha expirado
|
||||
#[allow(dead_code)]
|
||||
fn is_expired(&self) -> bool {
|
||||
Instant::now() > self.expiry
|
||||
}
|
||||
}
|
||||
|
||||
/// Servicio genérico de caché con TTL
|
||||
#[allow(dead_code)]
|
||||
pub struct CacheService<K, V> {
|
||||
cache: Arc<RwLock<HashMap<K, CacheEntry<V>>>>,
|
||||
ttl: Duration,
|
||||
max_entries: usize,
|
||||
}
|
||||
|
||||
impl<K, V> CacheService<K, V>
|
||||
where
|
||||
K: Hash + Eq + Clone + Send + Sync + 'static + std::fmt::Debug,
|
||||
V: Clone + Send + Sync + 'static,
|
||||
{
|
||||
/// Crea un nuevo servicio de caché
|
||||
#[allow(dead_code)]
|
||||
pub fn new(ttl: Duration, max_entries: usize) -> Self {
|
||||
Self {
|
||||
cache: Arc::new(RwLock::new(HashMap::new())),
|
||||
ttl,
|
||||
max_entries,
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtiene un valor de la caché o lo inserta si no existe
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_or_insert<F, E>(&self, key: K, loader: F) -> Result<V, DomainError>
|
||||
where
|
||||
F: FnOnce() -> Result<V, E>,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
// Intentar leer de la caché primero
|
||||
{
|
||||
let cache = self.cache.read().await;
|
||||
if let Some(entry) = cache.get(&key) {
|
||||
if !entry.is_expired() {
|
||||
tracing::debug!("Cache hit for key: {:?}", key);
|
||||
return Ok(entry.value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss o entrada expirada, obtener valor y actualizar
|
||||
let value = loader().map_err(|e| {
|
||||
DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"Cache",
|
||||
format!("Failed to load value for cache: {}", e),
|
||||
)
|
||||
.with_source(e)
|
||||
})?;
|
||||
|
||||
// Insertar en la caché
|
||||
{
|
||||
let mut cache = self.cache.write().await;
|
||||
|
||||
// Si alcanzamos el límite, eliminar una entrada aleatoria
|
||||
if cache.len() >= self.max_entries {
|
||||
if let Some(expired_key) = cache
|
||||
.iter()
|
||||
.find(|(_, v)| v.is_expired())
|
||||
.map(|(k, _)| k.clone())
|
||||
{
|
||||
cache.remove(&expired_key);
|
||||
} else if let Some(random_key) = cache.keys().next().cloned() {
|
||||
cache.remove(&random_key);
|
||||
}
|
||||
}
|
||||
|
||||
cache.insert(key.clone(), CacheEntry::new(value.clone(), self.ttl));
|
||||
}
|
||||
|
||||
tracing::debug!("Cache miss for key: {:?}, value loaded and cached", key);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Invalida una entrada específica de la caché
|
||||
#[allow(dead_code)]
|
||||
pub async fn invalidate(&self, key: &K) {
|
||||
let mut cache = self.cache.write().await;
|
||||
cache.remove(key);
|
||||
tracing::debug!("Cache entry invalidated for key: {:?}", key);
|
||||
}
|
||||
|
||||
/// Invalida todas las entradas de la caché
|
||||
#[allow(dead_code)]
|
||||
pub async fn invalidate_all(&self) {
|
||||
let mut cache = self.cache.write().await;
|
||||
cache.clear();
|
||||
tracing::debug!("Cache fully invalidated");
|
||||
}
|
||||
|
||||
/// Obtiene el número de entradas en la caché
|
||||
#[allow(dead_code)]
|
||||
pub async fn len(&self) -> usize {
|
||||
self.cache.read().await.len()
|
||||
}
|
||||
|
||||
/// Limpia las entradas expiradas de la caché
|
||||
#[allow(dead_code)]
|
||||
pub async fn cleanup_expired(&self) -> usize {
|
||||
let mut cache = self.cache.write().await;
|
||||
let initial_len = cache.len();
|
||||
|
||||
cache.retain(|_, v| !v.is_expired());
|
||||
|
||||
let removed = initial_len - cache.len();
|
||||
if removed > 0 {
|
||||
tracing::debug!("Removed {} expired cache entries", removed);
|
||||
}
|
||||
|
||||
removed
|
||||
}
|
||||
}
|
||||
|
||||
/// Caché específica para metadatos de archivos
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct FileMetadata {
|
||||
pub size: u64,
|
||||
pub created_at: u64,
|
||||
pub modified_at: u64,
|
||||
pub is_dir: bool,
|
||||
}
|
||||
|
||||
/// Gestor de caché para operaciones comunes de almacenamiento
|
||||
#[allow(dead_code)]
|
||||
pub struct CacheManager {
|
||||
/// Caché para metadatos de archivos/carpetas
|
||||
metadata_cache: CacheService<std::path::PathBuf, FileMetadata>,
|
||||
/// Caché para verificación de existencia de archivos
|
||||
existence_cache: CacheService<std::path::PathBuf, bool>,
|
||||
}
|
||||
|
||||
impl CacheManager {
|
||||
/// Crea un nuevo gestor de caché
|
||||
#[allow(dead_code)]
|
||||
pub fn new(metadata_ttl: Duration, existence_ttl: Duration) -> Self {
|
||||
Self {
|
||||
metadata_cache: CacheService::new(metadata_ttl, 10000), // Caché para 10,000 elementos
|
||||
existence_cache: CacheService::new(existence_ttl, 20000), // Caché para 20,000 elementos
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtiene o carga los metadatos de un archivo/carpeta
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_metadata<F>(&self, path: std::path::PathBuf, loader: F) -> Result<FileMetadata, DomainError>
|
||||
where
|
||||
F: FnOnce() -> Result<FileMetadata, std::io::Error>,
|
||||
{
|
||||
self.metadata_cache.get_or_insert(path, loader).await
|
||||
}
|
||||
|
||||
/// Verifica o determina si un archivo/carpeta existe
|
||||
#[allow(dead_code)]
|
||||
pub async fn check_exists<F>(&self, path: std::path::PathBuf, checker: F) -> Result<bool, DomainError>
|
||||
where
|
||||
F: FnOnce() -> Result<bool, std::io::Error>,
|
||||
{
|
||||
self.existence_cache.get_or_insert(path, checker).await
|
||||
}
|
||||
|
||||
/// Invalida la caché para una ruta específica
|
||||
#[allow(dead_code)]
|
||||
pub async fn invalidate_path(&self, path: &std::path::Path) {
|
||||
self.metadata_cache.invalidate(&path.to_path_buf()).await;
|
||||
self.existence_cache.invalidate(&path.to_path_buf()).await;
|
||||
}
|
||||
|
||||
/// Limpia todas las entradas expiradas
|
||||
#[allow(dead_code)]
|
||||
pub async fn cleanup(&self) -> (usize, usize) {
|
||||
let metadata_cleaned = self.metadata_cache.cleanup_expired().await;
|
||||
let existence_cleaned = self.existence_cache.cleanup_expired().await;
|
||||
(metadata_cleaned, existence_cleaned)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
use std::time::Duration;
|
||||
|
||||
/// Configuración de timeouts para diferentes operaciones
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimeoutConfig {
|
||||
/// Timeout para operaciones de archivo (ms)
|
||||
pub file_operation_ms: u64,
|
||||
/// Timeout para operaciones de directorio (ms)
|
||||
pub dir_operation_ms: u64,
|
||||
/// Timeout para adquisición de locks (ms)
|
||||
pub lock_acquisition_ms: u64,
|
||||
/// Timeout para operaciones de red (ms)
|
||||
#[allow(dead_code)]
|
||||
pub network_operation_ms: u64,
|
||||
}
|
||||
|
||||
impl Default for TimeoutConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
file_operation_ms: 10000, // 10 segundos
|
||||
dir_operation_ms: 30000, // 30 segundos
|
||||
lock_acquisition_ms: 5000, // 5 segundos
|
||||
network_operation_ms: 15000, // 15 segundos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeoutConfig {
|
||||
/// Obtiene un Duration para operaciones de archivo
|
||||
pub fn file_timeout(&self) -> Duration {
|
||||
Duration::from_millis(self.file_operation_ms)
|
||||
}
|
||||
|
||||
/// Obtiene un Duration para operaciones de directorio
|
||||
pub fn dir_timeout(&self) -> Duration {
|
||||
Duration::from_millis(self.dir_operation_ms)
|
||||
}
|
||||
|
||||
/// Obtiene un Duration para adquisición de locks
|
||||
pub fn lock_timeout(&self) -> Duration {
|
||||
Duration::from_millis(self.lock_acquisition_ms)
|
||||
}
|
||||
|
||||
/// Obtiene un Duration para operaciones de red
|
||||
#[allow(dead_code)]
|
||||
pub fn network_timeout(&self) -> Duration {
|
||||
Duration::from_millis(self.network_operation_ms)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuración para manejo de recursos grandes
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResourceConfig {
|
||||
/// Umbral en MB para considerar un archivo como grande
|
||||
pub large_file_threshold_mb: u64,
|
||||
/// Umbral de entradas para considerar un directorio como grande
|
||||
#[allow(dead_code)]
|
||||
pub large_dir_threshold_entries: usize,
|
||||
/// Tamaño de chunk para procesamiento de archivos grandes (bytes)
|
||||
pub chunk_size_bytes: usize,
|
||||
/// Límite de tamaño de archivo para cargar en memoria (MB)
|
||||
pub max_in_memory_file_size_mb: u64,
|
||||
}
|
||||
|
||||
impl Default for ResourceConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
large_file_threshold_mb: 100, // 100 MB
|
||||
large_dir_threshold_entries: 1000, // 1000 entradas
|
||||
chunk_size_bytes: 1024 * 1024, // 1 MB
|
||||
max_in_memory_file_size_mb: 50, // 50 MB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceConfig {
|
||||
/// Convierte un tamaño en bytes a MB
|
||||
pub fn bytes_to_mb(&self, bytes: u64) -> u64 {
|
||||
bytes / (1024 * 1024)
|
||||
}
|
||||
|
||||
/// Determina si un archivo es considerado grande
|
||||
pub fn is_large_file(&self, size_bytes: u64) -> bool {
|
||||
self.bytes_to_mb(size_bytes) >= self.large_file_threshold_mb
|
||||
}
|
||||
|
||||
/// Determina si un archivo es suficientemente grande para procesamiento paralelo
|
||||
pub fn needs_parallel_processing(&self, size_bytes: u64, config: &ConcurrencyConfig) -> bool {
|
||||
self.bytes_to_mb(size_bytes) >= config.min_size_for_parallel_chunks_mb
|
||||
}
|
||||
|
||||
/// Determina si un archivo puede cargarse completo en memoria
|
||||
pub fn can_load_in_memory(&self, size_bytes: u64) -> bool {
|
||||
self.bytes_to_mb(size_bytes) <= self.max_in_memory_file_size_mb
|
||||
}
|
||||
|
||||
/// Determina si un directorio es considerado grande
|
||||
#[allow(dead_code)]
|
||||
pub fn is_large_directory(&self, entry_count: usize) -> bool {
|
||||
entry_count >= self.large_dir_threshold_entries
|
||||
}
|
||||
|
||||
/// Calcula el número de chunks para procesamiento paralelo
|
||||
pub fn calculate_optimal_chunks(&self, size_bytes: u64, config: &ConcurrencyConfig) -> usize {
|
||||
// Si el archivo no es suficientemente grande, retornar 1
|
||||
if !self.needs_parallel_processing(size_bytes, config) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Calcular el número de chunks basado en el tamaño
|
||||
let chunk_count = (size_bytes as usize + config.parallel_chunk_size_bytes - 1)
|
||||
/ config.parallel_chunk_size_bytes;
|
||||
|
||||
// Limitar al máximo de chunks en paralelo
|
||||
chunk_count.min(config.max_parallel_chunks)
|
||||
}
|
||||
|
||||
/// Calcula el tamaño óptimo de cada chunk para procesamiento paralelo
|
||||
pub fn calculate_chunk_size(&self, file_size: u64, chunk_count: usize) -> usize {
|
||||
if chunk_count <= 1 {
|
||||
return file_size as usize;
|
||||
}
|
||||
|
||||
// Distribuir equitativamente el tamaño entre los chunks
|
||||
((file_size as usize) + chunk_count - 1) / chunk_count
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuración para operaciones concurrentes
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConcurrencyConfig {
|
||||
/// Máximo de tareas de archivo concurrentes
|
||||
pub max_concurrent_files: usize,
|
||||
/// Máximo de tareas de directorio concurrentes
|
||||
#[allow(dead_code)]
|
||||
pub max_concurrent_dirs: usize,
|
||||
/// Máximo de operaciones de IO concurrentes
|
||||
pub max_concurrent_io: usize,
|
||||
/// Máximo de chunks para procesar en paralelo por archivo
|
||||
pub max_parallel_chunks: usize,
|
||||
/// Tamaño mínimo de archivo (MB) para aplicar procesamiento paralelo de chunks
|
||||
pub min_size_for_parallel_chunks_mb: u64,
|
||||
/// Tamaño de chunk para procesamiento paralelo (bytes)
|
||||
pub parallel_chunk_size_bytes: usize,
|
||||
}
|
||||
|
||||
impl Default for ConcurrencyConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_concurrent_files: 10,
|
||||
max_concurrent_dirs: 5,
|
||||
max_concurrent_io: 20,
|
||||
max_parallel_chunks: 8,
|
||||
min_size_for_parallel_chunks_mb: 200, // 200 MB
|
||||
parallel_chunk_size_bytes: 8 * 1024 * 1024, // 8 MB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuración global de la aplicación
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppConfig {
|
||||
/// Configuración de timeouts
|
||||
pub timeouts: TimeoutConfig,
|
||||
/// Configuración de recursos
|
||||
pub resources: ResourceConfig,
|
||||
/// Configuración de concurrencia
|
||||
pub concurrency: ConcurrencyConfig,
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
timeouts: TimeoutConfig::default(),
|
||||
resources: ResourceConfig::default(),
|
||||
concurrency: ConcurrencyConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtenemos una configuración global por defecto
|
||||
#[allow(dead_code)]
|
||||
pub fn default_config() -> AppConfig {
|
||||
AppConfig::default()
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::domain::services::path_service::PathService;
|
||||
use crate::infrastructure::repositories::folder_fs_repository::FolderFsRepository;
|
||||
use crate::infrastructure::repositories::file_fs_repository::FileFsRepository;
|
||||
use crate::infrastructure::services::file_system_i18n_service::FileSystemI18nService;
|
||||
use crate::infrastructure::services::id_mapping_service::IdMappingService;
|
||||
use crate::infrastructure::services::cache_manager::StorageCacheManager;
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::application::services::file_service::FileService;
|
||||
use crate::application::services::i18n_application_service::I18nApplicationService;
|
||||
use crate::application::services::storage_mediator::{StorageMediator, FileSystemStorageMediator};
|
||||
use crate::application::ports::inbound::{FileUseCase, FolderUseCase, UseCaseFactory};
|
||||
use crate::application::ports::outbound::{FileStoragePort, FolderStoragePort};
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::services::i18n_service::I18nService;
|
||||
|
||||
/// Fábrica para los diferentes componentes de la aplicación
|
||||
#[allow(dead_code)]
|
||||
pub struct AppServiceFactory {
|
||||
storage_path: PathBuf,
|
||||
locales_path: PathBuf,
|
||||
}
|
||||
|
||||
impl AppServiceFactory {
|
||||
/// Crea una nueva fábrica de servicios
|
||||
#[allow(dead_code)]
|
||||
pub fn new(storage_path: PathBuf, locales_path: PathBuf) -> Self {
|
||||
Self {
|
||||
storage_path,
|
||||
locales_path,
|
||||
}
|
||||
}
|
||||
|
||||
/// Inicializa los servicios base del sistema
|
||||
#[allow(dead_code)]
|
||||
pub async fn create_core_services(&self) -> Result<CoreServices, DomainError> {
|
||||
// Path service
|
||||
let path_service = Arc::new(PathService::new(self.storage_path.clone()));
|
||||
|
||||
// Cache manager
|
||||
// TTL values in milliseconds and max entries for cache
|
||||
let file_ttl_ms = 60_000; // 1 minute for files
|
||||
let dir_ttl_ms = 120_000; // 2 minutes for directories
|
||||
let max_entries = 10_000; // Maximum cache entries
|
||||
let cache_manager = Arc::new(StorageCacheManager::new(file_ttl_ms, dir_ttl_ms, max_entries));
|
||||
|
||||
// Iniciar tarea de limpieza de caché en segundo plano
|
||||
let cache_manager_clone = cache_manager.clone();
|
||||
tokio::spawn(async move {
|
||||
StorageCacheManager::start_cleanup_task(cache_manager_clone).await;
|
||||
});
|
||||
|
||||
// ID mapping service
|
||||
let id_mapping_path = self.storage_path.join("folder_ids.json");
|
||||
let id_mapping_service = Arc::new(
|
||||
IdMappingService::new(id_mapping_path).await?
|
||||
);
|
||||
|
||||
Ok(CoreServices {
|
||||
path_service,
|
||||
cache_manager,
|
||||
id_mapping_service,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inicializa los servicios de repositorio
|
||||
#[allow(dead_code)]
|
||||
pub fn create_repository_services(&self, core: &CoreServices) -> RepositoryServices {
|
||||
// Storage mediator - create first because it's needed by folder repository
|
||||
// (temporarily using a placeholder for folder repository, will update later)
|
||||
let placeholder_folder_repo = Arc::new(FolderFsRepository::new_stub());
|
||||
|
||||
let storage_mediator = Arc::new(FileSystemStorageMediator::new(
|
||||
placeholder_folder_repo.clone(),
|
||||
core.path_service.clone(),
|
||||
core.id_mapping_service.clone()
|
||||
));
|
||||
|
||||
// Folder repository
|
||||
let folder_repository = Arc::new(FolderFsRepository::new(
|
||||
self.storage_path.clone(),
|
||||
storage_mediator.clone(),
|
||||
core.id_mapping_service.clone(),
|
||||
core.path_service.clone(),
|
||||
));
|
||||
|
||||
// Create a file metadata cache with default configuration
|
||||
let metadata_cache = Arc::new(
|
||||
crate::infrastructure::services::file_metadata_cache::FileMetadataCache::default_with_config(
|
||||
crate::common::config::AppConfig::default()
|
||||
)
|
||||
);
|
||||
|
||||
// File repository
|
||||
let file_repository = Arc::new(FileFsRepository::new(
|
||||
self.storage_path.clone(),
|
||||
storage_mediator.clone(),
|
||||
core.id_mapping_service.clone(),
|
||||
core.path_service.clone(),
|
||||
metadata_cache,
|
||||
));
|
||||
|
||||
// I18n repository
|
||||
let i18n_repository = Arc::new(FileSystemI18nService::new(
|
||||
self.locales_path.clone()
|
||||
));
|
||||
|
||||
RepositoryServices {
|
||||
folder_repository,
|
||||
file_repository,
|
||||
i18n_repository,
|
||||
storage_mediator,
|
||||
}
|
||||
}
|
||||
|
||||
/// Inicializa los servicios de aplicación
|
||||
#[allow(dead_code)]
|
||||
pub fn create_application_services(&self, repos: &RepositoryServices) -> ApplicationServices {
|
||||
// Servicios principales
|
||||
let folder_service = Arc::new(FolderService::new(
|
||||
repos.folder_repository.clone()
|
||||
));
|
||||
|
||||
let file_service = Arc::new(FileService::new(
|
||||
repos.file_repository.clone()
|
||||
));
|
||||
|
||||
let i18n_service = Arc::new(I18nApplicationService::new(
|
||||
repos.i18n_repository.clone()
|
||||
));
|
||||
|
||||
ApplicationServices {
|
||||
folder_service,
|
||||
file_service,
|
||||
i18n_service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contenedor para servicios base
|
||||
#[allow(dead_code)]
|
||||
pub struct CoreServices {
|
||||
pub path_service: Arc<PathService>,
|
||||
pub cache_manager: Arc<StorageCacheManager>,
|
||||
pub id_mapping_service: Arc<IdMappingService>,
|
||||
}
|
||||
|
||||
/// Contenedor para servicios de repositorio
|
||||
#[allow(dead_code)]
|
||||
pub struct RepositoryServices {
|
||||
pub folder_repository: Arc<dyn FolderStoragePort>,
|
||||
pub file_repository: Arc<dyn FileStoragePort>,
|
||||
pub i18n_repository: Arc<dyn I18nService>,
|
||||
pub storage_mediator: Arc<dyn StorageMediator>,
|
||||
}
|
||||
|
||||
/// Contenedor para servicios de aplicación
|
||||
#[allow(dead_code)]
|
||||
pub struct ApplicationServices {
|
||||
pub folder_service: Arc<dyn FolderUseCase>,
|
||||
pub file_service: Arc<dyn FileUseCase>,
|
||||
pub i18n_service: Arc<I18nApplicationService>,
|
||||
}
|
||||
|
||||
/// Fábrica de casos de uso para la inyección de dependencias
|
||||
#[allow(dead_code)]
|
||||
pub struct AppUseCaseFactory {
|
||||
services: ApplicationServices,
|
||||
}
|
||||
|
||||
impl AppUseCaseFactory {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(services: ApplicationServices) -> Self {
|
||||
Self { services }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UseCaseFactory for AppUseCaseFactory {
|
||||
fn create_file_use_case(&self) -> Arc<dyn FileUseCase> {
|
||||
self.services.file_service.clone()
|
||||
}
|
||||
|
||||
fn create_folder_use_case(&self) -> Arc<dyn FolderUseCase> {
|
||||
self.services.folder_service.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||
use std::error::Error as StdError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Tipos de errores comunes en toda la aplicación
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
/// Entidad no encontrada
|
||||
NotFound,
|
||||
/// Entidad ya existe
|
||||
AlreadyExists,
|
||||
/// Entrada inválida o validación fallida
|
||||
InvalidInput,
|
||||
/// Error de acceso o permisos
|
||||
AccessDenied,
|
||||
/// Tiempo de espera agotado
|
||||
Timeout,
|
||||
/// Error interno del sistema
|
||||
InternalError,
|
||||
}
|
||||
|
||||
impl Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
match self {
|
||||
ErrorKind::NotFound => write!(f, "Not Found"),
|
||||
ErrorKind::AlreadyExists => write!(f, "Already Exists"),
|
||||
ErrorKind::InvalidInput => write!(f, "Invalid Input"),
|
||||
ErrorKind::AccessDenied => write!(f, "Access Denied"),
|
||||
ErrorKind::Timeout => write!(f, "Timeout"),
|
||||
ErrorKind::InternalError => write!(f, "Internal Error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error base de dominio que proporciona contexto detallado
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{kind}: {message}")]
|
||||
pub struct DomainError {
|
||||
/// Tipo de error
|
||||
pub kind: ErrorKind,
|
||||
/// Tipo de entidad afectada (ej: "File", "Folder")
|
||||
pub entity_type: &'static str,
|
||||
/// Identificador de la entidad si está disponible
|
||||
pub entity_id: Option<String>,
|
||||
/// Mensaje descriptivo del error
|
||||
pub message: String,
|
||||
/// Error fuente (opcional)
|
||||
#[source]
|
||||
pub source: Option<Box<dyn StdError + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl DomainError {
|
||||
/// Crea un nuevo error de dominio
|
||||
pub fn new<S: Into<String>>(
|
||||
kind: ErrorKind,
|
||||
entity_type: &'static str,
|
||||
message: S,
|
||||
) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de entidad no encontrada
|
||||
pub fn not_found<S: Into<String>>(entity_type: &'static str, entity_id: S) -> Self {
|
||||
let id = entity_id.into();
|
||||
Self {
|
||||
kind: ErrorKind::NotFound,
|
||||
entity_type,
|
||||
entity_id: Some(id.clone()),
|
||||
message: format!("{} not found: {}", entity_type, id),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de entidad ya existente
|
||||
pub fn already_exists<S: Into<String>>(entity_type: &'static str, entity_id: S) -> Self {
|
||||
let id = entity_id.into();
|
||||
Self {
|
||||
kind: ErrorKind::AlreadyExists,
|
||||
entity_type,
|
||||
entity_id: Some(id.clone()),
|
||||
message: format!("{} already exists: {}", entity_type, id),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de tiempo agotado
|
||||
pub fn timeout<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Timeout,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error interno
|
||||
pub fn internal_error<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::InternalError,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de acceso denegado
|
||||
pub fn access_denied<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::AccessDenied,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de validación
|
||||
pub fn validation_error<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::InvalidInput,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Establece el ID de la entidad
|
||||
#[allow(dead_code)]
|
||||
pub fn with_id<S: Into<String>>(mut self, entity_id: S) -> Self {
|
||||
self.entity_id = Some(entity_id.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el error fuente
|
||||
pub fn with_source<E: StdError + Send + Sync + 'static>(mut self, source: E) -> Self {
|
||||
self.source = Some(Box::new(source));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait para añadir contexto a los errores
|
||||
pub trait ErrorContext<T, E> {
|
||||
fn with_context<C, F>(self, context: F) -> Result<T, DomainError>
|
||||
where
|
||||
C: Into<String>,
|
||||
F: FnOnce() -> C;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn with_error_kind(self, kind: ErrorKind, entity_type: &'static str) -> Result<T, DomainError>;
|
||||
}
|
||||
|
||||
impl<T, E: StdError + Send + Sync + 'static> ErrorContext<T, E> for Result<T, E> {
|
||||
fn with_context<C, F>(self, context: F) -> Result<T, DomainError>
|
||||
where
|
||||
C: Into<String>,
|
||||
F: FnOnce() -> C,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
DomainError {
|
||||
kind: ErrorKind::InternalError,
|
||||
entity_type: "Unknown",
|
||||
entity_id: None,
|
||||
message: context().into(),
|
||||
source: Some(Box::new(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn with_error_kind(self, kind: ErrorKind, entity_type: &'static str) -> Result<T, DomainError> {
|
||||
self.map_err(|e| {
|
||||
DomainError {
|
||||
kind,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: format!("{}", e),
|
||||
source: Some(Box::new(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro para convertir errores específicos a DomainError
|
||||
#[macro_export]
|
||||
macro_rules! impl_from_error {
|
||||
($error_type:ty, $entity_type:expr) => {
|
||||
impl From<$error_type> for DomainError {
|
||||
fn from(err: $error_type) -> Self {
|
||||
DomainError {
|
||||
kind: ErrorKind::InternalError,
|
||||
entity_type: $entity_type,
|
||||
entity_id: None,
|
||||
message: format!("{}", err),
|
||||
source: Some(Box::new(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Implementación para errores estándar comunes
|
||||
impl_from_error!(std::io::Error, "IO");
|
||||
impl_from_error!(serde_json::Error, "Serialization");
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod errors;
|
||||
pub mod config;
|
||||
pub mod cache;
|
||||
pub mod di;
|
||||
Reference in New Issue
Block a user