Files
pdf/gateway/app/services/convert/errors.py
T

57 lines
2.0 KiB
Python

"""Typed convert / validation error codes (Firecrawl anydoc-inspired taxonomy).
The codes exist so a caller can act on a failure instead of parsing prose. The
first seven describe a bad *input* and were the only ones the engine had, which
meant every failure after validation — engine bug, blown deadline, cancellation,
an output that would not open — arrived as an untyped 500 with a sentence in it.
The last four close that gap, so every error the engine can produce has a name.
"""
from __future__ import annotations
from enum import Enum
class ConvertErrorCode(str, Enum):
# The input is unusable, and the caller can tell why.
unsupported = "unsupported"
encrypted = "encrypted"
needs_ocr = "needs_ocr"
malformed = "malformed"
resource_limit = "resource_limit"
missing_part = "missing_part"
io = "io"
# The conversion itself did not complete. The distinction matters to a
# caller deciding whether to retry: `timeout` and `internal` may succeed on
# a second attempt, `cancelled` was asked for, and `invalid_output` means
# the engine produced something it will not stand behind.
timeout = "timeout"
cancelled = "cancelled"
invalid_output = "invalid_output"
internal = "internal"
# Default HTTP status per code
HTTP_STATUS: dict[ConvertErrorCode, int] = {
ConvertErrorCode.unsupported: 415,
ConvertErrorCode.encrypted: 400,
ConvertErrorCode.needs_ocr: 422,
ConvertErrorCode.malformed: 400,
ConvertErrorCode.resource_limit: 413,
ConvertErrorCode.missing_part: 400,
ConvertErrorCode.io: 400,
ConvertErrorCode.timeout: 504,
ConvertErrorCode.cancelled: 409,
ConvertErrorCode.invalid_output: 500,
ConvertErrorCode.internal: 500,
}
# Codes whose cause is transient: the same request may well succeed on a retry.
# Published so a client does not have to hardcode the list.
RETRYABLE_CODES = frozenset(
{
ConvertErrorCode.timeout,
ConvertErrorCode.internal,
}
)