mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-30 00:27:11 +09:00
76 lines
2.3 KiB
Python
76 lines
2.3 KiB
Python
"""Typed application errors rendered consistently at the HTTP boundary.
|
|
|
|
Services may raise these errors when a caller can act on the failure. Flask
|
|
owns their JSON representation; service code therefore does not need to know
|
|
about ``jsonify`` or duplicate status-code mapping.
|
|
|
|
The subclasses of built-in exception types are intentional compatibility
|
|
bridges. Existing callers that catch ``ValueError`` or ``LookupError`` keep
|
|
working while a feature is migrated incrementally to the typed contract.
|
|
"""
|
|
|
|
from collections.abc import Mapping
|
|
|
|
|
|
class ApplicationError(Exception):
|
|
"""Base class for expected, client-safe application failures."""
|
|
|
|
status_code = 500
|
|
code = 'application_error'
|
|
default_message = 'Application error'
|
|
|
|
def __init__(self, message=None, *, code=None, details=None):
|
|
self.message = str(message or self.default_message)
|
|
self.code = code or type(self).code
|
|
self.details = dict(details) if isinstance(details, Mapping) else None
|
|
super().__init__(self.message)
|
|
|
|
def to_dict(self, *, request_id=None):
|
|
"""Return the stable public error body used by API handlers."""
|
|
payload = {
|
|
'error': self.message,
|
|
'status': self.status_code,
|
|
'code': self.code,
|
|
}
|
|
if self.details:
|
|
payload['details'] = self.details
|
|
if request_id:
|
|
payload['request_id'] = request_id
|
|
return payload
|
|
|
|
|
|
class ValidationError(ApplicationError, ValueError):
|
|
status_code = 400
|
|
code = 'validation_error'
|
|
default_message = 'Invalid request'
|
|
|
|
|
|
class AuthenticationError(ApplicationError):
|
|
status_code = 401
|
|
code = 'authentication_required'
|
|
default_message = 'Authentication required'
|
|
|
|
|
|
class PermissionDeniedError(ApplicationError, PermissionError):
|
|
status_code = 403
|
|
code = 'permission_denied'
|
|
default_message = 'Access denied'
|
|
|
|
|
|
class NotFoundError(ApplicationError, LookupError):
|
|
status_code = 404
|
|
code = 'not_found'
|
|
default_message = 'Resource not found'
|
|
|
|
|
|
class ConflictError(ApplicationError):
|
|
status_code = 409
|
|
code = 'conflict'
|
|
default_message = 'Resource conflict'
|
|
|
|
|
|
class DependencyUnavailableError(ApplicationError):
|
|
status_code = 503
|
|
code = 'dependency_unavailable'
|
|
default_message = 'Dependency unavailable'
|