140 lines
3.4 KiB
Python
140 lines
3.4 KiB
Python
"""Custom exceptions for VoIdea application."""
|
|
|
|
from typing import Any
|
|
|
|
|
|
class AppError(Exception):
|
|
"""Base application exception.
|
|
|
|
All business errors inherit from this class.
|
|
Does not contain HTTP status codes (those are in API layer).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
code: str = "GENERAL_ERROR",
|
|
details: dict | None = None,
|
|
) -> None:
|
|
self.message = message
|
|
self.code = code
|
|
self.details = details or {}
|
|
super().__init__(message)
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Convert exception to dictionary."""
|
|
return {
|
|
"error": self.code,
|
|
"message": self.message,
|
|
"details": self.details,
|
|
}
|
|
|
|
|
|
class NotFoundError(AppError):
|
|
"""Resource not found error."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Resource not found",
|
|
code: str = "NOT_FOUND",
|
|
resource: str | None = None,
|
|
resource_id: str | None = None,
|
|
) -> None:
|
|
details = {}
|
|
if resource:
|
|
details["resource"] = resource
|
|
if resource_id:
|
|
details["resource_id"] = resource_id
|
|
super().__init__(message, code, details)
|
|
|
|
|
|
class PermissionError(AppError):
|
|
"""Permission denied error."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Permission denied",
|
|
code: str = "PERMISSION_DENIED",
|
|
) -> None:
|
|
super().__init__(message, code)
|
|
|
|
|
|
class ValidationError(AppError):
|
|
"""Validation error."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Validation error",
|
|
code: str = "VALIDATION_ERROR",
|
|
field: str | None = None,
|
|
value: Any = None,
|
|
) -> None:
|
|
details = {}
|
|
if field:
|
|
details["field"] = field
|
|
if value is not None:
|
|
details["value"] = str(value)
|
|
super().__init__(message, code, details)
|
|
|
|
|
|
class AuthenticationError(AppError):
|
|
"""Authentication error."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Authentication failed",
|
|
code: str = "AUTH_FAILED",
|
|
) -> None:
|
|
super().__init__(message, code)
|
|
|
|
|
|
class QuotaExceededError(AppError):
|
|
"""Quota exceeded error."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Quota exceeded",
|
|
code: str = "QUOTA_EXCEEDED",
|
|
limit: int | None = None,
|
|
used: int | None = None,
|
|
) -> None:
|
|
details = {}
|
|
if limit is not None:
|
|
details["limit"] = limit
|
|
if used is not None:
|
|
details["used"] = used
|
|
super().__init__(message, code, details)
|
|
|
|
|
|
class ExternalServiceError(AppError):
|
|
"""External service error (AI, OAuth, etc.)."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "External service error",
|
|
code: str = "EXTERNAL_SERVICE_ERROR",
|
|
service: str | None = None,
|
|
) -> None:
|
|
details = {}
|
|
if service:
|
|
details["service"] = service
|
|
super().__init__(message, code, details)
|
|
|
|
|
|
class RateLimitError(AppError):
|
|
"""Rate limit exceeded error."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str = "Rate limit exceeded",
|
|
code: str = "RATE_LIMIT_EXCEEDED",
|
|
retry_after: int | None = None,
|
|
) -> None:
|
|
details = {}
|
|
if retry_after:
|
|
details["retry_after"] = retry_after
|
|
super().__init__(message, code, details)
|
|
|
|
|
|
# Import for type hints
|
|
from typing import Any |