Files
FastapiAdmin/backend/app/core/exceptions.py
T

166 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
from typing import Any, Optional, List, Tuple, Union
from fastapi import Request, status
from fastapi.exceptions import RequestValidationError, ResponseValidationError
from pydantic_validation_decorator import FieldValidationError
from starlette.responses import JSONResponse
from starlette.exceptions import HTTPException
from sqlalchemy.exc import SQLAlchemyError
from pydantic_core import ErrorDetails
from pydantic import ValidationError
from app.common.constant import RET
from app.common.response import ErrorResponse
from app.core.logger import logger
class CustomException(Exception):
"""自定义异常基类"""
def __init__(
self,
msg: Optional[str] = RET.EXCEPTION.msg,
code: int = RET.EXCEPTION.code,
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
data: Optional[Any] = None,
success: bool = False
) -> None:
"""
初始化异常
:param msg: 错误消息
:param code: 业务状态码
:param status_code: HTTP状态码
:param data: 附加数据
"""
super().__init__(msg) # 调用父类初始化方法
self.status_code = status_code
self.code = code
self.msg = msg
self.data = data
self.success = success
def __str__(self) -> str:
"""返回异常消息"""
return self.msg
async def CustomExceptionHandler(request: Request, exc: CustomException) -> JSONResponse:
"""自定义异常处理器"""
logger.error(f"请求地址: {request.url}, 错误信息: {exc.msg}, 错误详情: {exc.data}")
return ErrorResponse(msg=exc.msg, code=exc.code, status_code=exc.status_code, data=exc.data)
async def HttpExceptionHandler(request: Request, exc: HTTPException) -> JSONResponse:
"""HTTP异常处理器"""
logger.error(f"请求地址: {request.url}, 错误详情: {exc.detail}")
return ErrorResponse(msg=exc.detail, status_code=exc.status_code)
async def ValidationExceptionHandler(request: Request, exc: RequestValidationError) -> JSONResponse:
"""请求参数验证异常处理器"""
msg:List[ErrorDetails] = custom_convert_errors(exc)
logger.error(f"请求地址: {request.url}, 错误信息: {msg}, 错误详情: {exc}")
return ErrorResponse(msg=str(msg), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, data=exc.body)
async def ResponseValidationHandle(request: Request, exc: ResponseValidationError) -> JSONResponse:
logger.error(f"请求地址: {request.url}, 错误详情: {exc}")
return ErrorResponse(msg=str(exc), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=exc.body)
async def SQLAlchemyExceptionHandler(request: Request, exc: SQLAlchemyError) -> JSONResponse:
"""数据库异常处理器"""
error_msg = f'数据库操作失败: {exc}'
logger.error(f"请求地址: {request.url}, 错误详情: {error_msg}")
return ErrorResponse(msg=error_msg, status_code=status.HTTP_400_BAD_REQUEST, data=str(exc))
async def ValueExceptionHandler(request: Request, exc: ValueError) -> JSONResponse:
"""值异常处理器"""
logger.error(f"请求地址: {request.url}, 错误详情: {exc}")
return ErrorResponse(msg=str(exc))
async def FieldValidationExceptionHandler(request: Request, exc: FieldValidationError) -> JSONResponse:
"""字段验证异常处理器"""
logger.error(f"请求地址: {request.url}, 错误信息: {exc.message}, 错误详情: {exc}")
return ErrorResponse(msg=str(exc))
async def AllExceptionHandler(request: Request, exc: Exception) -> JSONResponse:
"""全局异常处理器"""
logger.error(f"请求地址: {request.url}, 错误详情: {exc}")
return ErrorResponse(msg='服务器内部错误', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=str(exc))
ERROR_MAPPING = {
"missing": "请求失败,缺少必填项!",
# 字符串
"string_pattern_mismatch": "值错误,提交参数不满足正则表达式{pattern}!",
"string_too_long": "值错误,提交参数长度必须小于等于{max_length}!",
"string_too_short": "值错误,提交参数长度必须大于等于{min_length}!",
"string_type": "类型错误,提交参数应该为字符串!",
# 列表
"list_type": "类型错误,提交参数应该为列表!",
# 字典
"dict_type": "类型错误,提交参数应该为字典!",
# 集合
"set_type": "类型错误,提交参数应该为集合!",
# 元组
"tuple_type": "类型错误,提交参数应该为元组!",
# 元素数量
"too_long": "数量错误,提交参数的元素数量必须小于等于{max_length}!",
"too_short": "数量错误,提交参数的元素数量必须大于等于{min_length}!",
# 大小比值
"less_than_equal": "值错误,提交参数必须小于等于{le}!",
"greater_than_equal": "值错误,提交参数必须大于等于{ge}!",
"less_than": "值错误,提交参数必须小于{lt}!",
"greater_than": "值错误,提交参数必须大于{gt}!",
# 布尔值
"bool_type": "类型错误,提交参数应该为布尔值!",
"bool_parsing": "类型错误,提交参数应该为布尔值!",
# 字节
"bytes_type": "类型错误,提交参数应该为字节!",
"bytes_too_long": "值错误,提交参数长度必须小于等于{max_length}!",
"bytes_too_short": "值错误,提交参数长度必须大于等于{min_length}!",
# 整数
"int_parsing": "类型错误,提交参数应该为整数!",
"int_type": "类型错误,提交参数应该为整数!",
# 浮点数
"float_parsing": "类型错误,提交参数应该为浮点数!",
"float_type": "类型错误,提交参数应该为浮点数!",
# 日期时间
"date_parsing": "类型错误,提交参数应该为日期!",
"date_type": "类型错误,提交参数应该为日期!",
"time_parsing": "类型错误,提交参数应该为时间!",
"time_type": "类型错误,提交参数应该为时间!",
# 其他
"literal_error": "值错误,提交参数值在为{expected}中一个!",
"extra_forbidden": "值错误,提交参数值不在允许范围内!",
}
def custom_convert_errors(e: ValidationError | RequestValidationError) -> List[ErrorDetails]:
new_errors: List[ErrorDetails] = []
for error in e.errors():
error['loc'] = loc_to_dot_sep(error['loc'])
custom_message = ERROR_MAPPING.get(error['type'])
if custom_message:
ctx = error.get('ctx')
error['msg'] = (
custom_message.format(**ctx) if ctx else custom_message
)
new_errors.append(error)
return new_errors
def loc_to_dot_sep(loc: Tuple[Union[str, int], ...]) -> str:
path = ''
for i, x in enumerate(loc):
if isinstance(x, str):
if i > 0:
path += '.'
path += x
elif isinstance(x, int):
path += f'[{x}]'
else:
raise TypeError('Unexpected type')
return path