refactoring hexagonal architecture
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-23
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<T> = Result<T, FileError>;
|
||||
|
||||
// ============================================================================
|
||||
// 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<T> = Result<T, FolderError>;
|
||||
|
||||
// ============================================================================
|
||||
// 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<T> = Result<T, UserError>;
|
||||
|
||||
// ============================================================================
|
||||
// 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<T> = Result<T, ShareError>;
|
||||
|
||||
// ============================================================================
|
||||
// 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<T> = Result<T, CalendarError>;
|
||||
|
||||
// ============================================================================
|
||||
// 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<T> = Result<T, CalendarEventError>;
|
||||
|
||||
// ============================================================================
|
||||
// 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<E: Error>() {}
|
||||
|
||||
assert_error::<FileError>();
|
||||
assert_error::<FolderError>();
|
||||
assert_error::<UserError>();
|
||||
assert_error::<ShareError>();
|
||||
assert_error::<CalendarError>();
|
||||
assert_error::<CalendarEventError>();
|
||||
}
|
||||
}
|
||||
@@ -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<T> = Result<T, FileError>;
|
||||
// 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.
|
||||
|
||||
@@ -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<T> = Result<T, FolderError>;
|
||||
// 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)]
|
||||
|
||||
@@ -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;
|
||||
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,
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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<T> = Result<T, UserError>;
|
||||
// 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
|
||||
|
||||
@@ -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::<serde_json::Value>(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"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user