""" Common exceptions that can be useful throughout the app. """ from flask import jsonify from core import http_status class APIError(Exception): """ Base class for error API responses. Supposed to be raised in API views and handled by a handler registered in the app or in a blueprint. This class can be used directly, but more convenient is to subclass it for each type of error and override code, status_code and detail attributes. Using this class and its subclasses is preferred over abort() function, because it enforces consistent response format across all endpoints. Attributes: detail (str): Description that clarifies the cause of the error. It is addressed to developers who integrate the API with another application (e.g. web front-end), but not to the end user, because it can contain technical details. code (str): Unique identifier of an error. API consumer can use it to unambiguously identify the error and take appropriate actions, e.g. show human readable message to the end user on front-end. status_code (int): HTTP status code that must be used in response by error handler. extra: Optional arbitrary JSON-serializable structure containing data associated with the error. In case of validation failure it can be a mapping between field names and corresponding errors. In most of the cases however this attribute is None. """ detail = None code = None status_code = None extra = None def __init__(self, detail=None, code=None, status_code=None, extra=None): if detail: self.detail = detail if code: self.code = code if status_code: self.status_code = status_code if extra: self.extra = extra def to_dict(self): """ Serialize exception instance to a dictionary. Primary use case is to prepare exception for dumping it to JSON and adding to response body. Resulting dictionary always contains `code` and `detail` attributes. `extra` attribute is also added if it is not None. Returns: dict: Exception serialized to a dictionary. """ result = {"code": self.code, "detail": self.detail} if self.extra: result["extra"] = self.extra return result def respond(self): """ Generate JSON response with appropriate status code and body. Status code is taken from the status_code attribute of the instance. Body is generated by to_dict() method. Returns: flask.Response """ response = jsonify(self.to_dict()) response.status_code = self.status_code return response class BadRequest(APIError): """ HTTP 400 Bad Request. """ detail = "Bad Request." code = "bad_request" status_code = http_status.HTTP_400_BAD_REQUEST class JsonValidationError(BadRequest): """ Raised if incoming JSON payload does not pass validation. """ detail = "Invalid JSON payload." code = "validation_error.json" class QueryParamsValidationError(BadRequest): """ Raised if query string parameters validation failed. """ detail = "Invalid query parameters." code = "validation_error.query_params" class Unauthorized(APIError): """ HTTP 401 Unauthorized. """ detail = "Unauthorized." code = "unauthorized" status_code = http_status.HTTP_401_UNAUTHORIZED class NotFound(APIError): """ HTTP 404 Not Found. """ detail = "Not Found." code = "not_found" status_code = http_status.HTTP_404_NOT_FOUND class Conflict(APIError): """ HTTP 409 Conflict. """ detail = "Conflict." code = "conflict" status_code = http_status.HTTP_409_CONFLICT class UnsupportedMediaType(APIError): """ HTTP 415 Unsupported Media Type. """ detail = "Unsupported Media Type." code = "unsupported_media_type" status_code = http_status.HTTP_415_UNSUPPORTED_MEDIA_TYPE class TooManyRequests(APIError): """ HTTP 429 Too Many Requests. """ detail = "Too Many Requests." code = "too_many_requests" status_code = http_status.HTTP_429_TOO_MANY_REQUESTS class BadGateway(APIError): """ HTTP 502 Bad Gateway. This exception may be raised if a third party API returns invalid response. """ detail = "Bad Gateway." code = "bad_gateway" status_code = http_status.HTTP_502_BAD_GATEWAY class ServiceUnavailable(APIError): """ HTTP 503 Service Unavailable. This exception is usually raised when a third party API we rely on is unavailable. """ detail = "Service Unavailable." code = "service_unavailable" status_code = http_status.HTTP_503_SERVICE_UNAVAILABLE