Exception Handling - NFSandbox/sh_trade_backend GitHub Wiki
All custom error class and general custom error handling logic should be written inside exception
directory.
We use FastAPI Error Handler feature to handle all custom error derived from BaseError
. So it's strongly
recommended to create a new subclass when a custom error is needed.
Unified the format of custom error is also of benifit for frontend developing, which allow us to handle error raised by API using a general error handler.
There are three members:
-
name
The display name of this error, may be shown at frontend. -
message
The brief description of this error, may be shown at frontend. -
status
Used by the FastAPI Custom Error Handler to determine the response HTTP Status Code.
Keep in mind that API response does NOT contain any class hierarchy info, the
name
will be the unique identifier of different errors.
-
name
Should only containlowercase alphabets
and_
. E.g.:auth_error
,token_expired
.
Just for beauty.
- One name could only be used to represent one error. Don't let different errors have same name, even if they are in same category.
For example token error could have several different subcategory, for example the token is invalid or token is
expired. In this case, do NOT using something like token_error
to represent all token error. Instead, using
token_expired
, invalid_token
etc to represent each sub error.
The BaseError
class is a sub-class of Python Exception, which means it's competible with Python error handling
process.
However in order to make our custom error system works with FastAPI, we need a Pydantic
version of our error class,
which is BaseErrorOut
.
But anyway when throwing error or creating subclasses, we don't need to consider this part of mechanism, and we only
need to create new class from BaseError
.
In one word, always use detail.name
to validate a certain type of error when requesting API endpoints.
Although our custom BaseError
and BaseErrorOut
provide a custom HTTP Status Code options, never validate error based on HTTP Status Code in production environments. There are several reasons of this rules, one is that when using CDN services (e.g. Cloudflare) to proxy the API endpoints, some of HTTP Status Code may be intercepted and redirect to a custom page. For example the 404 Not Found
error will generally be intercepted by Cloudflare and redirect to a 502 Bad Gateway
error page, which will cause error when you try to validate some error using 404
status code.
Generally, the only thing we should override in subclass of BaseError
is the __init__()
function. In which:
- Deal with initialize logic of this kind of error. Determine
name
,message
andstatus
based on the logic. - Call
super().__init__(name=..., message=..., status=...)
Recommend checking out TokenError
subclass for more info.
Code Snippet
class TokenError(BaseError):
"""
Raise when error occurred while verifying token.
Check out __init__() for more info.
"""
def __init__(
self,
message: str | None = None,
expired: bool | None = None,
role_not_match: bool | None = None,
no_token: bool | None = None,
) -> None:
final_name = 'token_error'
final_message = message
"""
Create an `TokenError` instance.
:param message:
:param expired: If `true`, indicates the token is expired.
:param role_not_match: If `true`, indicates the role are not match the requirements.
"""
if message is None:
message = 'Could not verify the user tokens'
if expired:
final_name = 'token_expired'
message = 'Token expired, try login again to get a new token'
if role_not_match:
final_name = 'token_role_not_match'
message = 'Current role are not match the requirements to perform this operation or access this resources'
if no_token:
final_name = 'token_required'
message = 'Could not found a valid token, try login to an valid account'
# only when message is None, then use presets, otherwise always use the original message passed.
if final_message is None:
final_message = message
super().__init__(
name=final_name,
message=final_message,
status=401
)
As you see, although we say that we need to use different name
for every sub-category error, we can still use a
same class to deal with errors in same category based on the actual requirements.
This project using APIDog as third party testing tools. When using APIDog to validate the exception returned by FastAPI
endpoints, you could make use of PostProcessor module in APIDogs to check the return body of the Response.
As the BaseErrorOut
Pydantic model indicates, all custom error in this project will be converted to the following JSON schema:
{
"detail": {
"name": "error_name",
"message": "A brief description of this error."
}
}
Then you could use PostProcessor in APIDogs to validate a specific exception by making assertion to $.detail.name
of the Response Body:
The example image above is showing how to validate an identical_seller_buyer
exception returned by API server.
When using PostProcessor to validate the exception, make sure you turned off "Validate Response":