"""Domain error types. Use these instead of bare exceptions so services/routers can map them to meaningful HTTP responses. Never swallow errors silently (see ARCHITECTURE.md §10). """ class DomainError(Exception): """Base class for all application-domain errors.""" class NotFoundError(DomainError): """Raised when an entity cannot be found by its identifier. May optionally carry a `message_key` + `data` pair (§3.5) so a router can surface a structured, i18n-ready refusal instead of a hardcoded string — see `DubbeldagError` for the established shape. Existing call sites that only pass `message` are unaffected: `message_key` defaults to `""`. """ def __init__( self, message: str, message_key: str = "", data: dict[str, object] | None = None, ) -> None: super().__init__(message) self.message_key = message_key self.data: dict[str, object] = data if data is not None else {} class ValidationError(DomainError): """Raised when input fails a business rule (not just schema validation). May optionally carry a `message_key` + `data` pair (§3.5); see `NotFoundError` above for the same shape and rationale. """ def __init__( self, message: str, message_key: str = "", data: dict[str, object] | None = None, ) -> None: super().__init__(message) self.message_key = message_key self.data: dict[str, object] = data if data is not None else {} class InsufficientDaysError(DomainError): """Raised when a parent doesn't have enough remaining days to plan.""" class InvalidDayTypeError(ValidationError): """Raised when a planned-day type is not recognized.""" class DubbeldagError(ValidationError): """Raised when a dubbeldag breaks the 15-month window or the 60-day quota. Carries an optional `message_key` + `data` pair (§3.5) alongside the human-readable `message` (kept for anything that only logs/prints the exception) so callers that need to surface this to the UI — see `PlanningService._validate_dubbeldagar_batch` — can build an i18n-ready `Rejection` instead of leaking this English string past the backend. """ def __init__( self, message: str, message_key: str = "", data: dict[str, object] | None = None, ) -> None: super().__init__(message) self.message_key = message_key self.data: dict[str, object] = data if data is not None else {} class AuthError(DomainError): """Raised when credentials are invalid or an auth operation fails.""" class UnauthorizedError(DomainError): """Raised when the caller lacks permission to perform an action.""" class ConflictError(DomainError): """Raised when a resource already exists (e.g. duplicate email on register).""" class InvitationError(ValidationError): """Raised when an invitation token is invalid, expired, or already redeemed."""