refactor: remove serde from domain entities for Clean Architecture compliance

- Remove Serialize/Deserialize from File, Folder, Session, User, Contact entities
- Create contact_persistence_dto.rs for JSONB persistence in infrastructure layer
- Update contact_pg_repository to use persistence DTOs
- Fix dependency on zip crate (downgrade from 7.2.0 to 2.1.0)
- Fix unused variable warnings in main.rs
- Move PathService import from domain to infrastructure
- Add missing fields to CoreServices and RepositoryServices
- Create proper service initialization in main.rs

Clean Architecture improvements:
- Domain layer no longer depends on serde framework
- Persistence concerns isolated to infrastructure layer
- TokenClaims in auth_service.rs is only exception (required for JWT)
This commit is contained in:
Dionisio
2026-02-02 23:56:40 +01:00
parent 6aceb07f3f
commit 52840e57df
88 changed files with 4286 additions and 4847 deletions
+13 -342
View File
@@ -1,255 +1,22 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::error::Error as StdError;
use thiserror::Error;
//! Errores de la aplicación
//!
//! Este módulo re-exporta los errores del dominio y define utilidades
//! para conversión de errores de infraestructura.
/// Tipo Result común para la aplicación con DomainError como error estándar
pub type Result<T> = std::result::Result<T, DomainError>;
// Re-exportar errores del dominio para compatibilidad
pub use crate::domain::errors::{DomainError, ErrorKind, Result};
/// 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,
/// Funcionalidad no implementada
NotImplemented,
/// Operación no soportada
UnsupportedOperation,
/// Error de base de datos
DatabaseError,
}
// Re-exportar AppError desde interfaces para compatibilidad hacia atrás
// NOTA: El lugar canónico de AppError es ahora crate::interfaces::errors
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"),
ErrorKind::NotImplemented => write!(f, "Not Implemented"),
ErrorKind::UnsupportedOperation => write!(f, "Unsupported Operation"),
ErrorKind::DatabaseError => write!(f, "Database 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 para operaciones no soportadas
pub fn operation_not_supported<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
Self::new(
ErrorKind::UnsupportedOperation,
entity_type,
message,
)
}
/// 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,
}
}
/// Alias for access_denied to maintain compatibility
pub fn unauthorized<S: Into<String>>(message: S) -> Self {
Self {
kind: ErrorKind::AccessDenied,
entity_type: "Authorization",
entity_id: None,
message: message.into(),
source: None,
}
}
/// Crea un error de base de datos
pub fn database_error<S: Into<String>>(message: S) -> Self {
Self {
kind: ErrorKind::DatabaseError,
entity_type: "Database",
entity_id: None,
message: message.into(),
source: None,
}
}
/// Crea un error de validación
pub fn validation_error<S: Into<String>>(message: S) -> Self {
Self {
kind: ErrorKind::InvalidInput,
entity_type: "Validation",
entity_id: None,
message: message.into(),
source: None,
}
}
/// Crea un error de funcionalidad no implementada
pub fn not_implemented<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
Self {
kind: ErrorKind::NotImplemented,
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) -> std::result::Result<T, DomainError>
where
C: Into<String>,
F: FnOnce() -> C;
#[allow(dead_code)]
fn with_error_kind(self, kind: ErrorKind, entity_type: &'static str) -> std::result::Result<T, DomainError>;
}
impl<T, E: StdError + Send + Sync + 'static> ErrorContext<T, E> for std::result::Result<T, E> {
fn with_context<C, F>(self, context: F) -> std::result::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) -> std::result::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 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 DomainError {
impl From<$error_type> for crate::domain::errors::DomainError {
fn from(err: $error_type) -> Self {
DomainError {
kind: ErrorKind::InternalError,
crate::domain::errors::DomainError {
kind: crate::domain::errors::ErrorKind::InternalError,
entity_type: $entity_type,
entity_id: None,
message: format!("{}", err),
@@ -260,102 +27,6 @@ macro_rules! impl_from_error {
};
}
// Implementación para errores estándar comunes
impl_from_error!(std::io::Error, "IO");
// Implementaciones para errores de infraestructura (sqlx, serde_json)
impl_from_error!(serde_json::Error, "Serialization");
impl_from_error!(sqlx::Error, "Database");
impl_from_error!(uuid::Error, "UUID");
// Error para capas HTTP/API
#[derive(Debug)]
pub struct AppError {
pub status_code: axum::http::StatusCode,
pub message: String,
pub error_type: String,
}
// Estructura de respuesta de error
#[derive(serde::Serialize)]
pub struct ErrorResponse {
pub status: String,
pub message: String,
pub error_type: String,
}
impl AppError {
pub fn new(status_code: axum::http::StatusCode, message: impl Into<String>, error_type: impl Into<String>) -> Self {
Self {
status_code,
message: message.into(),
error_type: error_type.into(),
}
}
pub fn bad_request(message: impl Into<String>) -> Self {
Self::new(axum::http::StatusCode::BAD_REQUEST, message, "BadRequest")
}
pub fn unauthorized(message: impl Into<String>) -> Self {
Self::new(axum::http::StatusCode::UNAUTHORIZED, message, "Unauthorized")
}
pub fn forbidden(message: impl Into<String>) -> Self {
Self::new(axum::http::StatusCode::FORBIDDEN, message, "Forbidden")
}
pub fn not_found(message: impl Into<String>) -> Self {
Self::new(axum::http::StatusCode::NOT_FOUND, message, "NotFound")
}
pub fn internal_error(message: impl Into<String>) -> Self {
Self::new(axum::http::StatusCode::INTERNAL_SERVER_ERROR, message, "InternalError")
}
pub fn method_not_allowed(message: impl Into<String>) -> Self {
Self::new(axum::http::StatusCode::METHOD_NOT_ALLOWED, message, "MethodNotAllowed")
}
pub fn conflict(message: impl Into<String>) -> Self {
Self::new(axum::http::StatusCode::CONFLICT, message, "Conflict")
}
pub fn unsupported_media_type(message: impl Into<String>) -> Self {
Self::new(axum::http::StatusCode::UNSUPPORTED_MEDIA_TYPE, message, "UnsupportedMediaType")
}
}
impl From<DomainError> for AppError {
fn from(err: DomainError) -> Self {
let status_code = match err.kind {
ErrorKind::NotFound => axum::http::StatusCode::NOT_FOUND,
ErrorKind::AlreadyExists => axum::http::StatusCode::CONFLICT,
ErrorKind::InvalidInput => axum::http::StatusCode::BAD_REQUEST,
ErrorKind::AccessDenied => axum::http::StatusCode::FORBIDDEN,
ErrorKind::Timeout => axum::http::StatusCode::REQUEST_TIMEOUT,
ErrorKind::InternalError => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
ErrorKind::NotImplemented => axum::http::StatusCode::NOT_IMPLEMENTED,
ErrorKind::UnsupportedOperation => axum::http::StatusCode::METHOD_NOT_ALLOWED,
ErrorKind::DatabaseError => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
};
Self {
status_code,
message: err.message,
error_type: err.kind.to_string(),
}
}
}
impl axum::response::IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let status = self.status_code;
let error_response = ErrorResponse {
status: status.to_string(),
message: self.message,
error_type: self.error_type,
};
let body = axum::Json(error_response);
(status, body).into_response()
}
}