"""Canonical domain error hierarchy for Aegis. Every service-layer error should be a subclass of :class:`DomainError`. The global exception handler in ``app.middleware.error_handler`` maps each concrete subclass to an appropriate HTTP status code so that services never depend on FastAPI. Existing code that imports from ``app.domain.exceptions`` continues to work — that module re-exports everything defined here. """ from __future__ import annotations class DomainError(Exception): """Base for all domain errors.""" def __init__(self, message: str, *, code: str = "DOMAIN_ERROR") -> None: self.message = message self.code = code super().__init__(message) # ── Entity lifecycle ────────────────────────────────────────────────── class EntityNotFoundError(DomainError): """A requested entity does not exist.""" def __init__(self, entity: str, identifier: str) -> None: super().__init__(f"{entity} not found: {identifier}", code="NOT_FOUND") self.entity = entity self.identifier = identifier class DuplicateEntityError(DomainError): """Creating an entity that already exists.""" def __init__(self, entity: str, field: str, value: str) -> None: super().__init__( f"{entity} with {field}='{value}' already exists", code="DUPLICATE", ) # ── State machine ──────────────────────────────────────────────────── class InvalidStateTransition(DomainError): """A state-machine transition is not allowed.""" def __init__( self, current_state: str, target_state: str, valid_transitions: list[str] | None = None, ) -> None: msg = f"Cannot transition from '{current_state}' to '{target_state}'" if valid_transitions: msg += f". Valid transitions: {valid_transitions}" super().__init__(msg, code="INVALID_TRANSITION") self.current_state = current_state self.target_state = target_state self.valid_transitions = valid_transitions or [] # ── Business rules ──────────────────────────────────────────────────── class BusinessRuleViolation(DomainError): """An operation violates a business invariant.""" def __init__(self, message: str) -> None: super().__init__(message, code="BUSINESS_RULE_VIOLATION") class InvalidOperationError(BusinessRuleViolation): """An operation is invalid in the current context. Kept for backward compatibility; new code should prefer :class:`BusinessRuleViolation` directly. """ def __init__(self, message: str) -> None: super().__init__(message) self.code = "INVALID_OPERATION" # ── Authorization ──────────────────────────────────────────────────── class PermissionViolation(DomainError): """The user lacks permissions for an action.""" def __init__(self, message: str = "Insufficient permissions") -> None: super().__init__(message, code="FORBIDDEN")