from __future__ import annotations import uuid, traceback from django.conf import settings from rest_framework.views import exception_handler as drf_exception_handler from rest_framework.response import Response from rest_framework import status from rest_framework.exceptions import ValidationError as DRFValidationError from pydantic import ValidationError as PydValidationError def envelope_exception_handler(exc, context): """ Envuelve *todas* las excepciones DRF en: { ok: false, code, message, errors?, hint?, trace_id, detail?(DEBUG) } """ resp = drf_exception_handler(exc, context) trace_id = str(uuid.uuid4()) if resp is not None: # DRF ya resolvió un status_code razonable code = getattr(exc, "default_code", "error") message = None if isinstance(exc, DRFValidationError): message = "Validation error" else: # fallback a string corto message = str(getattr(exc, "detail", "")) or exc.__class__.__name__ data = { "ok": False, "code": code, "message": message, "errors": resp.data, # DRF normaliza los errores aquí "hint": None, "trace_id": trace_id, } if settings.DEBUG: data["detail"] = _short_trace() return Response(data, status=resp.status_code) # Excepción no manejada por DRF -> 500 data = { "ok": False, "code": "server_error", "message": "Unexpected server error", "hint": None, "trace_id": trace_id, } if settings.DEBUG: data["detail"] = _short_trace() return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def _short_trace(): try: return "\n".join(traceback.format_exc().splitlines()[-6:]) except Exception: return None