5.2.3. Build definition class for error

To better handle errors during application runtime and allow the client to process them easily, we need to define some error standards ⇒ all errors in the application will be mapped to 1–2 standard errors, and this is the task we need to implement in this section.

Errors are divided into two types: Application – errors belonging to the application (mapped to HTTP Errors as 5xx) and Client – errors belonging to the client (mapped to HTTP Errors as 4xx).

Pre-setup

In the error directory, create files such as AppError.py, ClientError.py, main.py, detail.py, and __init__.py.

Open detail.py and add the following code.

from typing import List, Optional, TypedDict


class BaseErrorDetail(TypedDict, total=False):
    source: str
    desc: str


class ErrorDetails(TypedDict, total=False):
    reasons: Optional[List[BaseErrorDetail]]

5.2.3.1

Application Error

In AppError.py, add

A list of information for HTTP Server Errors.

from typing import Optional

from .detail import BaseErrorDetail, ErrorDetails

HTTPServerErrorDict = {
    "InternalServerError": {
        "Status": 500,
        "Code": "INTERNAL_SERVER_ERROR",
    },
    "NotImplemented": {
        "Status": 501,
        "Code": "NOT_IMPLEMENTED",
    },
    "BadGateway": {
        "Status": 502,
        "Code": "BAD_GATEWAY",
    },
    "ServiceUnavailable": {
        "Status": 503,
        "Code": "SERVICE_UNAVAILABLE",
    },
    "GatewayTimeout": {
        "Status": 504,
        "Code": "GATEWAY_TIMEOUT",
    },
    "HttpVersionNotSupported": {
        "Status": 505,
        "Code": "HTTP_VERSION_NOT_SUPPORTED",
    },
}

Next is the class definition of AppError.

class AppError(Exception):
    """
    Lớp định nghĩa lỗi từ App hoặc phản hồi từ App.
    """

    def __init__(
        self,
        message: str,
        status_code: int = 500,
        code: Optional[str] = None,
        details: Optional[ErrorDetails] = None,
    ):
        super().__init__(message)

        self.status_code = status_code
        self.code = code
        self.details = details

    @staticmethod
    def create_error_detail(source: str, desc: str) -> BaseErrorDetail:
        """
        Tạo ra một base error detail.

        @static
        @returns
        """
        return {
            "source": source,
            "desc": desc,
        }

    def add_error_detail(self, detail: BaseErrorDetail):
        """
        Thêm chi tiết lỗi vào tổng lỗi.

        @param detail - chi tiết lỗi.

        @returns
        """
        if not self.details:
            self.details = {"reasons": [detail]}
        elif "reasons" not in self.details or self.details["reasons"] is None:
            self.details["reasons"] = [detail]
        else:
            self.details["reasons"].append(detail)

    def as_http_error(self, name: str):
        """
        Xem error này chính là HTTP Error.

        @param name - tên loại của HTTP Error.
        """
        if name not in HTTPServerErrorDict:
            raise ValueError(f"Unknown HTTP error type: {name}")

        self.status_code = HTTPServerErrorDict[name]["Status"]
        self.code = HTTPServerErrorDict[name]["Code"]

    def to_plain(self):
        """
        Trả về error là một plain object.

        @returns
        """
        return {
            "message": str(self),
            "code": self.code,
            "details": self.details,
        }

5.2.3.2

5.2.3.3

Client Error

Similarly, in ClientError.py:

A list of HTTP Client Error information.

from typing import Optional


from .AppError import AppError
from .detail import BaseErrorDetail, ErrorDetails

HTTPClientErrorDict = {
    "BadRequest": {
        "Status": 400,
        "Code": "BAD_REQUEST",
    },
    "Unauthorized": {
        "Status": 401,
        "Code": "UNAUTHORIZED",
    },
    "Forbidden": {
        "Status": 403,
        "Code": "FORBIDDEN",
    },
    "NotFound": {
        "Status": 404,
        "Code": "NOT_FOUND",
    },
    "MethodNotAllowed": {
        "Status": 405,
        "Code": "METHOD_NOT_ALLOWED",
    },
    "RequestTimeout": {
        "Status": 408,
        "Code": "REQUEST_TIMEOUT",
    },
    "Conflict": {
        "Status": 409,
        "Code": "CONFLICT",
    },
    "Gone": {
        "Status": 410,
        "Code": "GONE",
    },
    "UnprocessableEntity": {
        "Status": 422,
        "Code": "UNPROCESSABLE_ENTITY",
    },
    "TooManyRequests": {
        "Status": 429,
        "Code": "TOO_MANY_REQUESTS",
    },
}

And the code for the ClientError class definition. ClientError will inherit from AppError.

class ClientError(AppError):
    """
    Lớp định nghĩa lỗi từ client hoặc là lỗi nội bộ của app.
    """

    def __init__(self, message: str, details: Optional[ErrorDetails] = None):
        super().__init__(
            message,
            HTTPClientErrorDict["BadRequest"]["Status"],
            HTTPClientErrorDict["BadRequest"]["Code"],
            details,
        )

    def as_http_error(self, name: str):
        """
        Xem error này chính là HTTP Error.

        @param name - tên loại của HTTP Error.
        """
        if name not in HTTPClientErrorDict:
            raise ValueError(f"Unknown HTTP client error type: {name}")

        self.status_code = HTTPClientErrorDict[name]["Status"]
        self.code = HTTPClientErrorDict[name]["Code"]

    @staticmethod
    def create_error_detail(source: str, desc: str) -> BaseErrorDetail:
        """
        Tạo ra một base error detail.

        @static
        @returns
        """
        return {
            "source": source,
            "desc": desc,
        }

5.2.3.4

5.2.3.5

Port

In main.py, add the following code.

from .ClientError import ClientError
from .AppError import AppError


def is_standard_error(err):
    """Kiểm tra xem nếu một lỗi có phải là lỗi tiêu chuẩn (AppError, ClientError).

    Args:
        err: lỗi cần kiểm tra.

    Returns:
        bool: `True` nếu như là chuẩn lỗi, `False` nếu như không phải
    """
    return isinstance(err, ClientError) or isinstance(err, AppError)

5.2.3.6

Here, we will add a function to check whether the passed object is a standard error.

Finally, in __init__.py, add the following code.

from .AppError import AppError, HTTPServerErrorDict
from .ClientError import ClientError, HTTPClientErrorDict
from .detail import BaseErrorDetail, ErrorDetails
from .main import is_standard_error

__all__ = [
    "AppError",
    "HTTPServerErrorDict",
    "ClientError",
    "HTTPClientErrorDict",
    "BaseErrorDetail",
    "ErrorDetails",
    "is_standard_error",
]

5.2.3.7