""" Domain exception hierarchy. Each exception maps to an HTTP status code and a machine-readable error_code. Routers raise these; exception handlers in main.py convert them to HTTP responses. """ from typing import Any class AppError(Exception): status_code: int = 500 error_code: str = "INTERNAL_ERROR" def __init__(self, message: str = "An unexpected error occurred", context: dict[str, Any] | None = None): self.message = message self.context = context or {} super().__init__(message) class ImageDecodeError(AppError): status_code = 400 error_code = "IMAGE_DECODE_ERROR" def __init__(self): super().__init__("Could not decode image. Ensure it is a valid JPEG, PNG, or WebP file.") class ImageTooLargeError(AppError): status_code = 413 error_code = "IMAGE_TOO_LARGE" def __init__(self, max_mb: int = 10, detail: str | None = None): super().__init__( detail or f"Image exceeds maximum size of {max_mb}MB.", context={"max_mb": max_mb}, ) class InvalidMimeTypeError(AppError): status_code = 415 error_code = "INVALID_MIME_TYPE" def __init__(self, received: str, allowed: set[str]): super().__init__( f"Unsupported file type '{received}'. Allowed: {', '.join(sorted(allowed))}.", context={"received": received, "allowed": sorted(allowed)}, ) class UnknownChallengeError(AppError): status_code = 400 error_code = "UNKNOWN_CHALLENGE" def __init__(self, challenge_type: str, valid: list[str]): super().__init__( f"Unknown challenge '{challenge_type}'.", context={"received": challenge_type, "valid": valid}, ) class ModelNotReadyError(AppError): status_code = 503 error_code = "MODEL_NOT_READY" def __init__(self, model_name: str): super().__init__( f"Model '{model_name}' is not loaded. Retry in a moment.", context={"model": model_name}, ) class RateLimitError(AppError): status_code = 429 error_code = "RATE_LIMIT_EXCEEDED" def __init__(self): super().__init__("Too many requests. Please slow down.")