93 lines
2.3 KiB
Python
93 lines
2.3 KiB
Python
"""Error types that map one-to-one onto ``hux.error.v1`` records."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
class HuxError(Exception):
|
|
"""Base class; ``status`` and ``code`` follow identity.schema.json#/$defs/error."""
|
|
|
|
status = 500
|
|
code = "invalid"
|
|
|
|
def __init__(self, message: str, details: list[str] | None = None) -> None:
|
|
super().__init__(message)
|
|
self.message = message
|
|
self.details = list(details or [])
|
|
|
|
def record(self) -> dict:
|
|
"""Serialise as a ``hux.error.v1`` record."""
|
|
body = {"schema": "hux.error.v1", "status": self.status, "code": self.code, "message": self.message[:280]}
|
|
if self.details:
|
|
body["details"] = [d[:280] for d in self.details[:32]]
|
|
return body
|
|
|
|
|
|
class Unauthorized(HuxError):
|
|
"""Identity headers missing, malformed or not trusted."""
|
|
|
|
status, code = 401, "unauthorized"
|
|
|
|
|
|
class Forbidden(HuxError):
|
|
"""Identity is valid but does not own the resource."""
|
|
|
|
status, code = 403, "forbidden"
|
|
|
|
|
|
class NotFound(HuxError):
|
|
"""Resource does not exist for this tenant."""
|
|
|
|
status, code = 404, "not_found"
|
|
|
|
|
|
class FlagOff(HuxError):
|
|
"""Card (or one of its dependencies) is disabled; indistinguishable from not found on purpose."""
|
|
|
|
status, code = 404, "flag_off"
|
|
|
|
|
|
class Conflict(HuxError):
|
|
"""If-Match revision mismatch or duplicate create."""
|
|
|
|
status, code = 409, "conflict"
|
|
|
|
|
|
class Invalid(HuxError):
|
|
"""Body failed contract validation."""
|
|
|
|
status, code = 400, "invalid"
|
|
|
|
|
|
class Unprocessable(HuxError):
|
|
"""Well-formed body that violates a rule (hash mismatch, policy violation)."""
|
|
|
|
status, code = 422, "unprocessable"
|
|
|
|
|
|
class TooLarge(HuxError):
|
|
"""Body, record or family exceeds its bound."""
|
|
|
|
status, code = 413, "too_large"
|
|
|
|
|
|
class RateLimited(HuxError):
|
|
"""The caller exceeded the bounded per-subject request rate."""
|
|
|
|
status, code = 429, "rate_limited"
|
|
|
|
def __init__(self, retry_after: int) -> None:
|
|
super().__init__("request rate limit exceeded")
|
|
self.retry_after = max(1, int(retry_after))
|
|
|
|
|
|
class ApprovalRequired(HuxError):
|
|
"""An action needs an approval record before it may proceed."""
|
|
|
|
status, code = 403, "approval_required"
|
|
|
|
|
|
class BudgetExhausted(HuxError):
|
|
"""A run budget has been spent."""
|
|
|
|
status, code = 429, "budget_exhausted"
|