diff --git a/src/application/services/share_service.rs b/src/application/services/share_service.rs index d6bdae8b..78182792 100644 --- a/src/application/services/share_service.rs +++ b/src/application/services/share_service.rs @@ -495,7 +495,7 @@ mod tests { let mut shares = self.shares.lock().unwrap(); if !shares.contains_key(&share.id) { - return Err(DomainError::NotFound(format!("Share with ID {} not found for update", share.id))); + return Err(DomainError::not_found("Share", &share.id)); } shares.insert(share.id.clone(), share.clone()); @@ -509,7 +509,7 @@ mod tests { // Find the share to get the token let share = shares.get(id) - .ok_or_else(|| DomainError::NotFound(format!("Share with ID {} not found for deletion", id)))?; + .ok_or_else(|| DomainError::not_found("Share", id))?; // Remove token mapping tokens.remove(&share.token); diff --git a/src/application/services/trash_service_test.rs b/src/application/services/trash_service_test.rs index 039be261..b3d31386 100644 --- a/src/application/services/trash_service_test.rs +++ b/src/application/services/trash_service_test.rs @@ -8,9 +8,10 @@ use crate::common::errors::{Result, DomainError}; use crate::domain::entities::file::File; use crate::domain::entities::folder::Folder; use crate::domain::entities::trashed_item::{TrashedItem, TrashedItemType}; -use crate::domain::repositories::file_repository::{FileRepository, FileRepositoryResult}; -use crate::domain::repositories::folder_repository::{FolderRepository, FolderRepositoryResult}; +use crate::domain::repositories::file_repository::{FileRepository, FileRepositoryResult, FileRepositoryError}; +use crate::domain::repositories::folder_repository::{FolderRepository, FolderRepositoryResult, FolderRepositoryError}; use crate::domain::repositories::trash_repository::TrashRepository; +use crate::domain::services::path_service::StoragePath; use crate::application::services::trash_service::TrashService; // Mock repositories for testing @@ -103,12 +104,11 @@ impl MockFileRepository { fn add_test_file(&self, id: &str, name: &str, path: &str) { let file = File::new( - Uuid::parse_str(id).unwrap(), + id.to_string(), name.to_string(), - path.to_string(), - "text/plain".to_string(), + StoragePath::from_string(path), 100, - Uuid::new_v4(), + "text/plain".to_string(), None, ).unwrap(); @@ -124,7 +124,7 @@ impl FileRepository for MockFileRepository { if let Some(file) = files.get(id) { Ok(file.clone()) } else { - Err("File not found".into()) + Err(FileRepositoryError::NotFound(id.to_string())) } } @@ -136,7 +136,7 @@ impl FileRepository for MockFileRepository { trashed.insert(id.to_string(), file); Ok(()) } else { - Err("File not found".into()) + Err(FileRepositoryError::NotFound(id.to_string())) } } @@ -148,7 +148,7 @@ impl FileRepository for MockFileRepository { files.insert(id.to_string(), file); Ok(()) } else { - Err("File not found in trash".into()) + Err(FileRepositoryError::NotFound(format!("File {} not found in trash", id))) } } @@ -157,7 +157,7 @@ impl FileRepository for MockFileRepository { if trashed.remove(id).is_some() { Ok(()) } else { - Err("File not found in trash".into()) + Err(FileRepositoryError::NotFound(format!("File {} not found in trash", id))) } } @@ -185,9 +185,9 @@ impl MockFolderRepository { fn add_test_folder(&self, id: &str, name: &str, path: &str) { let folder = Folder::new( - Uuid::parse_str(id).unwrap(), + id.to_string(), name.to_string(), - path.to_string(), + StoragePath::from_string(path), None, ).unwrap(); @@ -203,7 +203,7 @@ impl FolderRepository for MockFolderRepository { if let Some(folder) = folders.get(id) { Ok(folder.clone()) } else { - Err("Folder not found".into()) + Err(FolderRepositoryError::NotFound(id.to_string())) } } @@ -215,7 +215,7 @@ impl FolderRepository for MockFolderRepository { trashed.insert(id.to_string(), folder); Ok(()) } else { - Err("Folder not found".into()) + Err(FolderRepositoryError::NotFound(id.to_string())) } } @@ -227,7 +227,7 @@ impl FolderRepository for MockFolderRepository { folders.insert(id.to_string(), folder); Ok(()) } else { - Err("Folder not found in trash".into()) + Err(FolderRepositoryError::NotFound(format!("Folder {} not found in trash", id))) } } @@ -236,7 +236,7 @@ impl FolderRepository for MockFolderRepository { if trashed.remove(id).is_some() { Ok(()) } else { - Err("Folder not found in trash".into()) + Err(FolderRepositoryError::NotFound(format!("Folder {} not found in trash", id))) } } diff --git a/src/common/errors.rs b/src/common/errors.rs index 251a4ff3..62c8f832 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -1,7 +1,10 @@ //! Errores de la aplicación //! -//! Este módulo re-exporta los errores del dominio y define utilidades -//! para conversión de errores de infraestructura. +//! Este módulo re-exporta los errores del dominio para compatibilidad. +//! Las conversiones de errores de infraestructura (sqlx, serde_json, etc.) +//! se encuentran en infrastructure/adapters/error_adapters.rs, siguiendo +//! los principios de Clean Architecture donde el dominio no debe conocer +//! detalles de infraestructura. // Re-exportar errores del dominio para compatibilidad pub use crate::domain::errors::{DomainError, ErrorKind, Result}; @@ -9,24 +12,10 @@ pub use crate::domain::errors::{DomainError, ErrorKind, Result}; // Re-exportar AppError desde interfaces para compatibilidad hacia atrás // NOTA: El lugar canónico de AppError es ahora crate::interfaces::errors -// Macro para convertir errores específicos de infraestructura a DomainError -#[macro_export] -macro_rules! impl_from_error { - ($error_type:ty, $entity_type:expr) => { - impl From<$error_type> for crate::domain::errors::DomainError { - fn from(err: $error_type) -> Self { - crate::domain::errors::DomainError { - kind: crate::domain::errors::ErrorKind::InternalError, - entity_type: $entity_type, - entity_id: None, - message: format!("{}", err), - source: Some(Box::new(err)), - } - } - } - }; -} - -// Implementaciones para errores de infraestructura (sqlx, serde_json) -impl_from_error!(serde_json::Error, "Serialization"); -impl_from_error!(sqlx::Error, "Database"); +// Las conversiones de errores de infraestructura se han movido a: +// crate::infrastructure::adapters::error_adapters +// +// Para convertir errores de infraestructura a DomainError, use: +// - El trait IntoDomainError para conversiones explícitas con contexto +// - O maneje los errores en los repositorios/servicios de infraestructura +// usando map_err() con DomainError::internal_error() o métodos similares diff --git a/src/domain/entities/calendar.rs b/src/domain/entities/calendar.rs index 1fdad8af..4b35390f 100644 --- a/src/domain/entities/calendar.rs +++ b/src/domain/entities/calendar.rs @@ -11,27 +11,11 @@ use uuid::Uuid; use chrono::{DateTime, Utc}; -use thiserror::Error; use crate::common::errors::{Result, DomainError, ErrorKind}; -/** - * Error types specific to calendar operations. - */ -#[derive(Error, Debug)] -pub enum CalendarError { - /// Error when calendar name is invalid - #[error("Invalid calendar name: {0}")] - InvalidName(String), - - /// Error when color code is invalid - #[error("Invalid color code: {0}")] - InvalidColor(String), - - /// Error when owner ID is invalid - #[error("Invalid owner ID: {0}")] - InvalidOwnerId(String), -} +// Re-exportar errores de entidad desde el módulo centralizado +pub use super::entity_errors::CalendarError; /** * Calendar entity. diff --git a/src/domain/entities/calendar_event.rs b/src/domain/entities/calendar_event.rs index 13f0db7d..9970d681 100644 --- a/src/domain/entities/calendar_event.rs +++ b/src/domain/entities/calendar_event.rs @@ -11,31 +11,11 @@ use uuid::Uuid; use chrono::{DateTime, Utc, Duration, TimeZone}; -use thiserror::Error; use crate::common::errors::{Result, DomainError, ErrorKind}; -/** - * Error types specific to calendar event operations. - */ -#[derive(Error, Debug)] -pub enum CalendarEventError { - /// Error when event summary/title is invalid - #[error("Invalid event summary: {0}")] - InvalidSummary(String), - - /// Error when event dates are invalid - #[error("Invalid event dates: {0}")] - InvalidDates(String), - - /// Error when recurrence rule is invalid - #[error("Invalid recurrence rule: {0}")] - InvalidRecurrence(String), - - /// Error when iCalendar data is invalid - #[error("Invalid iCalendar data: {0}")] - InvalidICalData(String), -} +// Re-exportar errores de entidad desde el módulo centralizado +pub use super::entity_errors::CalendarEventError; /** * CalendarEvent entity. diff --git a/src/domain/entities/entity_errors.rs b/src/domain/entities/entity_errors.rs new file mode 100644 index 00000000..6f3f563c --- /dev/null +++ b/src/domain/entities/entity_errors.rs @@ -0,0 +1,254 @@ +//! Errores puros de entidades de dominio +//! +//! Este módulo define los errores específicos de las entidades de dominio +//! sin dependencias de frameworks externos, siguiendo los principios de +//! Clean Architecture. +//! +//! Los errores implementan manualmente `std::error::Error` y `std::fmt::Display` +//! para mantener el dominio libre de dependencias externas. + +use std::error::Error; +use std::fmt::{Display, Formatter, Result as FmtResult}; + +// ============================================================================ +// FILE ERRORS +// ============================================================================ + +/// Errores que pueden ocurrir durante operaciones con entidades File +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FileError { + /// Ocurre cuando el nombre de archivo contiene caracteres inválidos o está vacío + InvalidFileName(String), + /// Ocurre cuando falla la validación de cualquier atributo de la entidad + ValidationError(String), +} + +impl Display for FileError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + FileError::InvalidFileName(name) => write!(f, "Invalid file name: {}", name), + FileError::ValidationError(msg) => write!(f, "Validation error: {}", msg), + } + } +} + +impl Error for FileError {} + +/// Alias de tipo para resultados de operaciones con entidades File +pub type FileResult = Result; + +// ============================================================================ +// FOLDER ERRORS +// ============================================================================ + +/// Errores que pueden ocurrir durante operaciones con entidades Folder +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FolderError { + /// Ocurre cuando el nombre de carpeta contiene caracteres inválidos o está vacío + InvalidFolderName(String), + /// Ocurre cuando falla la validación de cualquier atributo de la entidad + ValidationError(String), +} + +impl Display for FolderError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + FolderError::InvalidFolderName(name) => write!(f, "Invalid folder name: {}", name), + FolderError::ValidationError(msg) => write!(f, "Validation error: {}", msg), + } + } +} + +impl Error for FolderError {} + +/// Alias de tipo para resultados de operaciones con entidades Folder +pub type FolderResult = Result; + +// ============================================================================ +// USER ERRORS +// ============================================================================ + +/// Errores que pueden ocurrir durante operaciones con entidades User +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum UserError { + /// Nombre de usuario inválido + InvalidUsername(String), + /// Contraseña inválida + InvalidPassword(String), + /// Error de validación general + ValidationError(String), + /// Error de autenticación + AuthenticationError(String), +} + +impl Display for UserError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + UserError::InvalidUsername(msg) => write!(f, "Username inválido: {}", msg), + UserError::InvalidPassword(msg) => write!(f, "Password inválido: {}", msg), + UserError::ValidationError(msg) => write!(f, "Error en la validación: {}", msg), + UserError::AuthenticationError(msg) => write!(f, "Error en la autenticación: {}", msg), + } + } +} + +impl Error for UserError {} + +/// Alias de tipo para resultados de operaciones con entidades User +pub type UserResult = Result; + +// ============================================================================ +// SHARE ERRORS +// ============================================================================ + +/// Errores que pueden ocurrir durante operaciones con entidades Share +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ShareError { + /// Token de compartición inválido + InvalidToken(String), + /// Fecha de expiración inválida + InvalidExpiration(String), + /// Error de validación general + ValidationError(String), +} + +impl Display for ShareError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + ShareError::InvalidToken(msg) => write!(f, "Invalid token: {}", msg), + ShareError::InvalidExpiration(msg) => write!(f, "Invalid expiration date: {}", msg), + ShareError::ValidationError(msg) => write!(f, "Validation error: {}", msg), + } + } +} + +impl Error for ShareError {} + +/// Alias de tipo para resultados de operaciones con entidades Share +pub type ShareResult = Result; + +// ============================================================================ +// CALENDAR ERRORS +// ============================================================================ + +/// Errores que pueden ocurrir durante operaciones con entidades Calendar +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CalendarError { + /// Nombre de calendario inválido + InvalidName(String), + /// Código de color inválido + InvalidColor(String), + /// ID de propietario inválido + InvalidOwnerId(String), +} + +impl Display for CalendarError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + CalendarError::InvalidName(msg) => write!(f, "Invalid calendar name: {}", msg), + CalendarError::InvalidColor(msg) => write!(f, "Invalid color code: {}", msg), + CalendarError::InvalidOwnerId(msg) => write!(f, "Invalid owner ID: {}", msg), + } + } +} + +impl Error for CalendarError {} + +/// Alias de tipo para resultados de operaciones con entidades Calendar +pub type CalendarResult = Result; + +// ============================================================================ +// CALENDAR EVENT ERRORS +// ============================================================================ + +/// Errores que pueden ocurrir durante operaciones con entidades CalendarEvent +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CalendarEventError { + /// Resumen/título de evento inválido + InvalidSummary(String), + /// Fechas de evento inválidas + InvalidDates(String), + /// Regla de recurrencia inválida + InvalidRecurrence(String), + /// Datos iCalendar inválidos + InvalidICalData(String), +} + +impl Display for CalendarEventError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + CalendarEventError::InvalidSummary(msg) => write!(f, "Invalid event summary: {}", msg), + CalendarEventError::InvalidDates(msg) => write!(f, "Invalid event dates: {}", msg), + CalendarEventError::InvalidRecurrence(msg) => write!(f, "Invalid recurrence rule: {}", msg), + CalendarEventError::InvalidICalData(msg) => write!(f, "Invalid iCalendar data: {}", msg), + } + } +} + +impl Error for CalendarEventError {} + +/// Alias de tipo para resultados de operaciones con entidades CalendarEvent +pub type CalendarEventResult = Result; + +// ============================================================================ +// TESTS +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_file_error_display() { + let err = FileError::InvalidFileName("test.txt".to_string()); + assert_eq!(err.to_string(), "Invalid file name: test.txt"); + + let err = FileError::ValidationError("size too large".to_string()); + assert_eq!(err.to_string(), "Validation error: size too large"); + } + + #[test] + fn test_folder_error_display() { + let err = FolderError::InvalidFolderName("my/folder".to_string()); + assert_eq!(err.to_string(), "Invalid folder name: my/folder"); + } + + #[test] + fn test_user_error_display() { + let err = UserError::InvalidUsername("".to_string()); + assert_eq!(err.to_string(), "Username inválido: "); + + let err = UserError::AuthenticationError("invalid credentials".to_string()); + assert_eq!(err.to_string(), "Error en la autenticación: invalid credentials"); + } + + #[test] + fn test_share_error_display() { + let err = ShareError::InvalidToken("abc123".to_string()); + assert_eq!(err.to_string(), "Invalid token: abc123"); + } + + #[test] + fn test_calendar_error_display() { + let err = CalendarError::InvalidColor("not-a-color".to_string()); + assert_eq!(err.to_string(), "Invalid color code: not-a-color"); + } + + #[test] + fn test_calendar_event_error_display() { + let err = CalendarEventError::InvalidDates("end before start".to_string()); + assert_eq!(err.to_string(), "Invalid event dates: end before start"); + } + + #[test] + fn test_errors_implement_error_trait() { + fn assert_error() {} + + assert_error::(); + assert_error::(); + assert_error::(); + assert_error::(); + assert_error::(); + assert_error::(); + } +} diff --git a/src/domain/entities/file.rs b/src/domain/entities/file.rs index 4493c6d6..fe7a3461 100644 --- a/src/domain/entities/file.rs +++ b/src/domain/entities/file.rs @@ -1,28 +1,7 @@ use crate::domain::services::path_service::StoragePath; -/** - * Represents errors that can occur during file entity operations. - * - * This enum encapsulates various error conditions that may arise when creating, - * validating, or manipulating file entities in the domain model. - */ -#[derive(Debug, thiserror::Error)] -pub enum FileError { - /// Occurs when a file name contains invalid characters or is empty. - #[error("Invalid file name: {0}")] - InvalidFileName(String), - - /// Occurs when validation fails for any file entity attribute. - #[error("Validation error: {0}")] - ValidationError(String), -} - -/** - * Type alias for results of file entity operations. - * - * Provides a convenient way to return either a successful value or a FileError. - */ -pub type FileResult = Result; +// Re-exportar errores de entidad desde el módulo centralizado +pub use super::entity_errors::{FileError, FileResult}; /** * Represents a file in the system's domain model. diff --git a/src/domain/entities/folder.rs b/src/domain/entities/folder.rs index e7a53270..a94e14ce 100644 --- a/src/domain/entities/folder.rs +++ b/src/domain/entities/folder.rs @@ -1,17 +1,7 @@ use crate::domain::services::path_service::StoragePath; -/// Error in the creation or manipulation of folder entities -#[derive(Debug, thiserror::Error)] -pub enum FolderError { - #[error("Invalid folder name: {0}")] - InvalidFolderName(String), - - #[error("Validation error: {0}")] - ValidationError(String), -} - -/// Result type for folder entity operations -pub type FolderResult = Result; +// Re-exportar errores de entidad desde el módulo centralizado +pub use super::entity_errors::{FolderError, FolderResult}; /// Represents a folder entity in the domain #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/domain/entities/mod.rs b/src/domain/entities/mod.rs index 9b4e81fc..f6dd0c46 100644 --- a/src/domain/entities/mod.rs +++ b/src/domain/entities/mod.rs @@ -1,9 +1,20 @@ pub mod calendar; pub mod calendar_event; pub mod contact; +pub mod entity_errors; pub mod file; pub mod folder; pub mod user; pub mod session; pub mod share; -pub mod trashed_item; \ No newline at end of file +pub mod trashed_item; + +// Re-exportar errores de entidades para facilitar el uso +pub use entity_errors::{ + FileError, FileResult, + FolderError, FolderResult, + UserError, UserResult, + ShareError, ShareResult, + CalendarError, CalendarResult, + CalendarEventError, CalendarEventResult, +}; \ No newline at end of file diff --git a/src/domain/entities/share.rs b/src/domain/entities/share.rs index 045ceb56..7499da6c 100644 --- a/src/domain/entities/share.rs +++ b/src/domain/entities/share.rs @@ -1,7 +1,9 @@ use std::time::{SystemTime, UNIX_EPOCH}; -use thiserror::Error; use uuid::Uuid; +// Re-exportar errores de entidad desde el módulo centralizado +pub use super::entity_errors::ShareError; + #[derive(Debug, Clone, PartialEq)] pub struct Share { pub id: String, @@ -29,16 +31,6 @@ pub enum ShareItemType { Folder, } -#[derive(Debug, Error)] -pub enum ShareError { - #[error("Invalid token: {0}")] - InvalidToken(String), - #[error("Invalid expiration date: {0}")] - InvalidExpiration(String), - #[error("Validation error: {0}")] - ValidationError(String), -} - impl Share { pub fn new( item_id: String, diff --git a/src/domain/entities/user.rs b/src/domain/entities/user.rs index 26bc1ed8..e91cbd53 100644 --- a/src/domain/entities/user.rs +++ b/src/domain/entities/user.rs @@ -1,22 +1,8 @@ use uuid::Uuid; use chrono::{DateTime, Utc}; -#[derive(Debug, thiserror::Error)] -pub enum UserError { - #[error("Username inválido: {0}")] - InvalidUsername(String), - - #[error("Password inválido: {0}")] - InvalidPassword(String), - - #[error("Error en la validación: {0}")] - ValidationError(String), - - #[error("Error en la autenticación: {0}")] - AuthenticationError(String), -} - -pub type UserResult = Result; +// Re-exportar errores de entidad desde el módulo centralizado +pub use super::entity_errors::{UserError, UserResult}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] // We'll handle conversion manually for now until the type is properly set up in the database diff --git a/src/infrastructure/adapters/error_adapters.rs b/src/infrastructure/adapters/error_adapters.rs new file mode 100644 index 00000000..554f9870 --- /dev/null +++ b/src/infrastructure/adapters/error_adapters.rs @@ -0,0 +1,125 @@ +//! Infrastructure Error Adapters +//! +//! This module contains error conversion adapters for infrastructure-specific errors. +//! These adapters bridge the gap between infrastructure errors (sqlx, serde_json, etc.) +//! and domain errors, keeping the domain layer clean of infrastructure knowledge. +//! +//! Following Clean Architecture principles, these conversions are placed in the +//! infrastructure layer rather than the common/domain layers. + +use crate::domain::errors::{DomainError, ErrorKind}; + +/// Macro to create From implementations for infrastructure errors to DomainError. +/// +/// This macro is intended for use ONLY within the infrastructure layer. +/// The domain layer should not depend on specific infrastructure error types. +/// +/// # Example +/// +/// ```ignore +/// // In infrastructure code: +/// impl_infra_error_to_domain!(serde_json::Error, "Serialization"); +/// impl_infra_error_to_domain!(sqlx::Error, "Database"); +/// ``` +#[macro_export] +macro_rules! impl_infra_error_to_domain { + ($error_type:ty, $entity_type:expr) => { + impl From<$error_type> for crate::domain::errors::DomainError { + fn from(err: $error_type) -> Self { + crate::domain::errors::DomainError { + kind: crate::domain::errors::ErrorKind::InternalError, + entity_type: $entity_type, + entity_id: None, + message: format!("{}", err), + source: Some(Box::new(err)), + } + } + } + }; +} + +// Note: We intentionally DO NOT create global From implementations for sqlx::Error +// or serde_json::Error here. Each repository/service should handle its own error +// conversions with proper context. This prevents the domain from depending on +// infrastructure error types. + +/// Helper trait for converting infrastructure errors to DomainError with context. +/// +/// This trait provides a more explicit way to convert infrastructure errors +/// to domain errors, requiring the caller to provide context about the entity +/// being operated on. +pub trait IntoDomainError { + /// Convert the error to a DomainError with the given entity type context. + fn into_domain_error(self, entity_type: &'static str) -> DomainError; +} + +impl IntoDomainError for std::io::Error { + fn into_domain_error(self, entity_type: &'static str) -> DomainError { + DomainError::new( + ErrorKind::InternalError, + entity_type, + format!("IO error: {}", self), + ).with_source(self) + } +} + +impl IntoDomainError for serde_json::Error { + fn into_domain_error(self, entity_type: &'static str) -> DomainError { + DomainError::new( + ErrorKind::InternalError, + entity_type, + format!("Serialization error: {}", self), + ).with_source(self) + } +} + +impl IntoDomainError for sqlx::Error { + fn into_domain_error(self, entity_type: &'static str) -> DomainError { + match &self { + sqlx::Error::RowNotFound => { + DomainError::not_found(entity_type, "Record not found") + } + sqlx::Error::Database(db_err) => { + // Handle specific PostgreSQL error codes + if db_err.code().map_or(false, |c| c == "23505") { + DomainError::already_exists(entity_type, "Record already exists") + } else { + DomainError::new( + ErrorKind::DatabaseError, + entity_type, + format!("Database error: {}", db_err), + ).with_source(self) + } + } + _ => DomainError::new( + ErrorKind::InternalError, + entity_type, + format!("Database error: {}", self), + ).with_source(self) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_io_error_conversion() { + let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"); + let domain_error = io_error.into_domain_error("File"); + + assert_eq!(domain_error.entity_type, "File"); + assert!(domain_error.message.contains("IO error")); + } + + #[test] + fn test_serde_json_error_conversion() { + let json_str = "{ invalid json }"; + let serde_error: serde_json::Error = serde_json::from_str::(json_str).unwrap_err(); + let domain_error = serde_error.into_domain_error("Config"); + + assert_eq!(domain_error.entity_type, "Config"); + assert!(domain_error.message.contains("Serialization error")); + } +} diff --git a/src/infrastructure/adapters/mod.rs b/src/infrastructure/adapters/mod.rs index 0311cc94..fe06fd01 100644 --- a/src/infrastructure/adapters/mod.rs +++ b/src/infrastructure/adapters/mod.rs @@ -3,9 +3,14 @@ //! This module contains adapters that bridge the gap between domain repositories //! and application ports. These adapters implement the application layer ports //! using the infrastructure layer repositories. +//! +//! It also includes error adapters for converting infrastructure-specific errors +//! to domain errors, following Clean Architecture principles. pub mod calendar_storage_adapter; pub mod contact_storage_adapter; +pub mod error_adapters; pub use calendar_storage_adapter::CalendarStorageAdapter; pub use contact_storage_adapter::ContactStorageAdapter; +pub use error_adapters::IntoDomainError;