"""Typované výjimky + centrální exception handlery. Platí pravidlo: žádná tichá selhání — každá chyba se loguje (bez secrets). """ from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from .logging_config import get_logger log = get_logger("audio-transcription.errors") class TranscriptionError(Exception): """Základní chyba služby s HTTP status kódem.""" status_code = 500 def __init__(self, message: str, status_code: int | None = None, detail=None): super().__init__(message) self.message = message if status_code is not None: self.status_code = status_code self.detail = detail class CredentialsError(TranscriptionError): """Chybějící / neplatné přihlašovací údaje (X- hlavičky).""" status_code = 401 class BadRequestError(TranscriptionError): """Neplatný vstup (chybí audio, špatné parametry).""" status_code = 400 class RateLimitError(TranscriptionError): """Upstream rate limit (HTTP 429).""" status_code = 429 class UpstreamError(TranscriptionError): """Chyba při volání upstream API (Deepgram / OpenAI).""" status_code = 502 def raise_for_upstream(upstream: str, status: int, body: str) -> None: """Zmapuje HTTP status z upstreamu (Deepgram/OpenAI) na správnou chybu služby. - 400/415/422 → 400 (špatný / nepodporovaný audio soubor = chyba klienta) - 401/403 → 401 (upstream odmítl API klíč volajícího) - 429 → 429 (rate limit) - jinak → 502 (výpadek / neočekávaná chyba upstreamu) """ snippet = (body or "")[:500] if status in (400, 415, 422): raise BadRequestError( f"{upstream} odmítl audio (HTTP {status}) — pravděpodobně nepodporovaný " f"formát nebo poškozený soubor.", detail=snippet, ) if status in (401, 403): raise CredentialsError( f"{upstream} odmítl API klíč (HTTP {status}).", detail=snippet, ) if status == 429: raise RateLimitError(f"{upstream} rate limit (HTTP 429).", detail=snippet) raise UpstreamError(f"{upstream} vrátil chybu {status}.", detail=snippet) def register_exception_handlers(app: FastAPI) -> None: @app.exception_handler(TranscriptionError) async def _handle_transcription_error(request: Request, exc: TranscriptionError): log.warning( "%s on %s: %s", exc.__class__.__name__, request.url.path, exc.message, ) body = {"error": exc.__class__.__name__, "message": exc.message} if exc.detail is not None: body["detail"] = exc.detail return JSONResponse(status_code=exc.status_code, content=body) @app.exception_handler(Exception) async def _handle_unexpected(request: Request, exc: Exception): # Nelogujeme celý stack s možnými secrets ve vstupu; logujeme typ + zprávu. log.error( "Unhandled %s on %s: %s", exc.__class__.__name__, request.url.path, exc, ) return JSONResponse( status_code=500, content={"error": "InternalError", "message": "Neočekávaná chyba serveru."}, )