"""Application errors. Every failure surfaces a machine readable error_code and a message in Czech, because the message is shown to the caller. """ from __future__ import annotations class ConversionError(Exception): """Base class for every error the conversion pipeline can raise.""" error_code = "internal_error" status_code = 500 def __init__(self, message: str, detail: dict | None = None) -> None: super().__init__(message) self.message = message self.detail = detail or {} def to_dict(self) -> dict: payload = {"error_code": self.error_code, "message": self.message} if self.detail: payload["detail"] = self.detail return payload class InvalidRequestError(ConversionError): error_code = "invalid_request" status_code = 400 class BlockedTargetError(ConversionError): """The requested URL points somewhere the service refuses to reach.""" error_code = "blocked_target" status_code = 400 class SourceUnavailableError(ConversionError): error_code = "source_unavailable" status_code = 502 class RenderTimeoutError(ConversionError): error_code = "render_timeout" status_code = 504 class SyncTooLongError(ConversionError): """Synchronous conversion exceeded its budget, caller should use /jobs.""" error_code = "sync_too_long" status_code = 413 class EngineUnavailableError(ConversionError): error_code = "engine_unavailable" status_code = 503 class UnsupportedCombinationError(ConversionError): error_code = "unsupported_combination" status_code = 400 class LimitExceededError(ConversionError): error_code = "limit_exceeded" status_code = 400 class JobNotFoundError(ConversionError): error_code = "job_not_found" status_code = 404 class QueueFullError(ConversionError): error_code = "queue_full" status_code = 503 ERROR_CLASSES = ( InvalidRequestError, BlockedTargetError, SourceUnavailableError, RenderTimeoutError, SyncTooLongError, EngineUnavailableError, UnsupportedCombinationError, LimitExceededError, JobNotFoundError, QueueFullError, ) STATUS_BY_CODE = {cls.error_code: cls.status_code for cls in ERROR_CLASSES} def error_from_code(error_code: str, message: str, detail: dict | None = None) -> ConversionError: """Rebuild a typed error from a stored job error.""" error = ConversionError(message, detail) error.error_code = error_code error.status_code = STATUS_BY_CODE.get(error_code, 500) return error