errors.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. from __future__ import annotations
  2. from enum import StrEnum
  3. from typing import Any
  4. class ErrorCode(StrEnum):
  5. RUN_START_FAILED = "RUN_START_FAILED"
  6. RUN_NOT_FOUND = "RUN_NOT_FOUND"
  7. INVALID_REQUEST = "INVALID_REQUEST"
  8. INVALID_SOURCE = "INVALID_SOURCE"
  9. DB_CONFIG_MISSING = "DB_CONFIG_MISSING"
  10. DB_CONNECTION_FAILED = "DB_CONNECTION_FAILED"
  11. DB_PERMISSION_DENIED = "DB_PERMISSION_DENIED"
  12. DB_SCHEMA_NOT_READY = "DB_SCHEMA_NOT_READY"
  13. RUNTIME_WRITE_FAILED = "RUNTIME_WRITE_FAILED"
  14. POLICY_BUNDLE_NOT_FOUND = "POLICY_BUNDLE_NOT_FOUND"
  15. PLATFORM_CONFIG_MISSING = "PLATFORM_CONFIG_MISSING"
  16. PLATFORM_REQUEST_FAILED = "PLATFORM_REQUEST_FAILED"
  17. QUERY_GENERATION_FAILED = "QUERY_GENERATION_FAILED"
  18. CONFIG_RULE_PACK_DISPATCH_CONFLICT = "CONFIG_RULE_PACK_DISPATCH_CONFLICT"
  19. SENSITIVE_KEYS = {
  20. "password",
  21. "token",
  22. "access_token",
  23. "refresh_token",
  24. "api_key",
  25. "apikey",
  26. "secret",
  27. "dsn",
  28. "authorization",
  29. "cookie",
  30. "session",
  31. "credential",
  32. }
  33. class ContentAgentError(Exception):
  34. def __init__(
  35. self,
  36. error_code: ErrorCode | str,
  37. message: str,
  38. detail: dict[str, Any] | None = None,
  39. status_code: int = 500,
  40. ) -> None:
  41. super().__init__(message)
  42. self.error_code = ErrorCode(error_code)
  43. self.message = message
  44. self.detail = sanitize_error_detail(detail or {})
  45. self.status_code = status_code
  46. def response_detail(self) -> dict[str, Any]:
  47. return error_response(self.error_code, self.message, self.detail)
  48. def error_response(
  49. error_code: ErrorCode | str,
  50. message: str,
  51. detail: dict[str, Any] | None = None,
  52. ) -> dict[str, Any]:
  53. return {
  54. "error_code": ErrorCode(error_code).value,
  55. "message": message,
  56. "detail": sanitize_error_detail(detail or {}),
  57. }
  58. def sanitize_error_detail(value: Any) -> Any:
  59. if isinstance(value, dict):
  60. result: dict[str, Any] = {}
  61. for key, item in value.items():
  62. lowered = str(key).lower()
  63. if lowered in SENSITIVE_KEYS:
  64. result[key] = "<redacted>"
  65. else:
  66. result[key] = sanitize_error_detail(item)
  67. return result
  68. if isinstance(value, list):
  69. return [sanitize_error_detail(item) for item in value]
  70. if isinstance(value, tuple):
  71. return [sanitize_error_detail(item) for item in value]
  72. return value
  73. def error_from_exception(
  74. exc: Exception,
  75. *,
  76. default_code: ErrorCode = ErrorCode.RUN_START_FAILED,
  77. detail: dict[str, Any] | None = None,
  78. ) -> ContentAgentError:
  79. if isinstance(exc, ContentAgentError):
  80. return exc
  81. return ContentAgentError(
  82. default_code,
  83. _safe_message(default_code),
  84. {
  85. **(detail or {}),
  86. "exception_type": type(exc).__name__,
  87. },
  88. )
  89. def _safe_message(error_code: ErrorCode) -> str:
  90. messages = {
  91. ErrorCode.RUN_START_FAILED: "run start failed",
  92. ErrorCode.RUNTIME_WRITE_FAILED: "runtime write failed",
  93. ErrorCode.INVALID_SOURCE: "invalid source",
  94. ErrorCode.INVALID_REQUEST: "invalid request",
  95. ErrorCode.POLICY_BUNDLE_NOT_FOUND: "policy bundle not found",
  96. ErrorCode.PLATFORM_CONFIG_MISSING: "platform config missing",
  97. ErrorCode.PLATFORM_REQUEST_FAILED: "platform request failed",
  98. ErrorCode.QUERY_GENERATION_FAILED: "query generation failed",
  99. ErrorCode.CONFIG_RULE_PACK_DISPATCH_CONFLICT: "rule pack dispatch conflict in config",
  100. }
  101. return messages.get(error_code, error_code.value.lower())