"""
Vairified SDK Errors
Custom exception classes for API errors.
"""
from typing import Any, Optional
[docs]
class VairifiedError(Exception):
"""
Base exception for Vairified API errors.
:ivar message: Error message.
:ivar status_code: HTTP status code (if applicable).
:ivar response: Raw response body (if available).
"""
[docs]
def __init__(
self,
message: str,
status_code: Optional[int] = None,
response: Optional[Any] = None,
):
"""
Initialize the error.
:param message: Human-readable error message.
:param status_code: HTTP status code.
:param response: Raw response body.
"""
super().__init__(message)
self.message = message
self.status_code = status_code
self.response = response
def __str__(self) -> str:
if self.status_code:
return f"[{self.status_code}] {self.message}"
return self.message
[docs]
class RateLimitError(VairifiedError):
"""
Raised when rate limit is exceeded.
:ivar retry_after: Seconds to wait before retrying.
"""
[docs]
def __init__(
self,
message: str = "Rate limit exceeded",
retry_after: Optional[int] = None,
**kwargs,
):
"""
Initialize rate limit error.
:param message: Error message.
:param retry_after: Seconds to wait before retrying.
"""
super().__init__(message, status_code=429, **kwargs)
self.retry_after = retry_after
[docs]
class AuthenticationError(VairifiedError):
"""
Raised when authentication fails.
Typically means the API key is invalid or expired.
"""
[docs]
def __init__(self, message: str = "Invalid API key", **kwargs):
"""
Initialize authentication error.
:param message: Error message.
"""
super().__init__(message, status_code=401, **kwargs)
[docs]
class NotFoundError(VairifiedError):
"""
Raised when a resource is not found.
Typically means the requested member/player doesn't exist.
"""
[docs]
def __init__(self, message: str = "Resource not found", **kwargs):
"""
Initialize not found error.
:param message: Error message.
"""
super().__init__(message, status_code=404, **kwargs)
[docs]
class ValidationError(VairifiedError):
"""
Raised when request validation fails.
Check the response body for details on which fields failed validation.
"""
[docs]
def __init__(self, message: str = "Validation error", **kwargs):
"""
Initialize validation error.
:param message: Error message.
"""
super().__init__(message, status_code=400, **kwargs)
[docs]
class OAuthError(VairifiedError):
"""
Raised when an OAuth operation fails.
This can occur during authorization, token exchange, refresh, or revocation.
:ivar error_code: OAuth error code (e.g., 'invalid_grant', 'expired_token').
"""
[docs]
def __init__(
self,
message: str = "OAuth error",
error_code: Optional[str] = None,
**kwargs,
):
"""
Initialize OAuth error.
:param message: Error message.
:param error_code: OAuth error code.
"""
super().__init__(message, **kwargs)
self.error_code = error_code
[docs]
class WebhookSignatureError(VairifiedError):
"""
Raised when a webhook delivery cannot be trusted.
:rotating_light: **The message never contains the signing secret, the received
digest, or the computed one.** Writing either digest into an error puts a valid
HMAC of the partner's own payload into their application logs, where it is an
oracle for anyone who can read them. Branch on :attr:`reason`; there is
deliberately nothing finer-grained to log.
:ivar reason: Why it was refused. One of:
``no_secret_configured``
No usable signing secret was supplied. This is **your** configuration,
not an attack -- almost always an unset environment variable. Separate
from ``signature_mismatch`` on purpose: reporting a missing secret as a
mismatch sends people hunting an attacker when the fix is one env var.
``invalid_option``
A caller-supplied option was not usable -- a non-numeric or negative
tolerance, a NaN clock. Separate from ``timestamp_out_of_tolerance``
for the same reason: that one means "this delivery's clock disagrees
with yours", and reporting your own typo under it sends people hunting
clock skew on a healthy box.
``missing_signature``
No ``X-Vairified-Signature`` header at all.
``malformed_signature``
Present but unparseable, or missing its ``t`` / ``v1`` parts.
``timestamp_out_of_tolerance``
Outside the replay window, in either direction.
``signature_mismatch``
Parsed fine; no supplied secret produces this digest.
``malformed_body``
Verified, but the body is not JSON, or is not a webhook envelope, or a
field the caller will read is absent or the wrong type.
"""
[docs]
def __init__(self, reason: str, message: str) -> None:
super().__init__(message)
self.reason = reason