From 35b968d28862ed1ff1c54950dfb13b0951a02ef2 Mon Sep 17 00:00:00 2001 From: dev1lfreak Date: Mon, 1 Jun 2026 15:07:44 +0300 Subject: [PATCH] feat: add new custom 500 exceptions --- src/application/domain/exceptions/__init__.py | 3 +++ .../domain/exceptions/data_base_error_exception.py | 8 ++++++++ .../domain/exceptions/jwt_error_exception.py | 8 ++++++++ .../domain/exceptions/rate_limit_error_exception.py | 8 ++++++++ .../database/repositories/user_repository.py | 12 ++++++------ src/infrastructure/security/jwt.py | 4 ++-- src/infrastructure/vault/keys.py | 4 ++-- src/presentation/decorators/rate_limit.py | 4 ++-- 8 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 src/application/domain/exceptions/data_base_error_exception.py create mode 100644 src/application/domain/exceptions/jwt_error_exception.py create mode 100644 src/application/domain/exceptions/rate_limit_error_exception.py diff --git a/src/application/domain/exceptions/__init__.py b/src/application/domain/exceptions/__init__.py index d5bb22b..ed764c1 100644 --- a/src/application/domain/exceptions/__init__.py +++ b/src/application/domain/exceptions/__init__.py @@ -8,3 +8,6 @@ from src.application.domain.exceptions.internal_exception import InternalExcepti from src.application.domain.exceptions.service_unavailable_exception import ServiceUnavailableException from src.application.domain.exceptions.too_many_requests_exception import TooManyRequestsException from src.application.domain.exceptions.csrf_error_exception import CsrfErrorException +from src.application.domain.exceptions.jwt_error_exception import JwtErrorException +from src.application.domain.exceptions.data_base_error_exception import DataBaseErrorException +from src.application.domain.exceptions.rate_limit_error_exception import RateLimitErrorException \ No newline at end of file diff --git a/src/application/domain/exceptions/data_base_error_exception.py b/src/application/domain/exceptions/data_base_error_exception.py new file mode 100644 index 0000000..93f790b --- /dev/null +++ b/src/application/domain/exceptions/data_base_error_exception.py @@ -0,0 +1,8 @@ +from src.application.domain.exceptions.application_exceptions import ApplicationException + +from typing import Mapping + + +class DataBaseErrorException(ApplicationException): + def __init__(self, message: str = 'Database error occurred', headers: Mapping[str, str] | None = None): + super().__init__(500, message, headers) \ No newline at end of file diff --git a/src/application/domain/exceptions/jwt_error_exception.py b/src/application/domain/exceptions/jwt_error_exception.py new file mode 100644 index 0000000..463c6d8 --- /dev/null +++ b/src/application/domain/exceptions/jwt_error_exception.py @@ -0,0 +1,8 @@ +from src.application.domain.exceptions.application_exceptions import ApplicationException + +from typing import Mapping + + +class JwtErrorException(ApplicationException): + def __init__(self, message: str = 'JWT error occurred', headers: Mapping[str, str] | None = None): + super().__init__(500, message, headers) \ No newline at end of file diff --git a/src/application/domain/exceptions/rate_limit_error_exception.py b/src/application/domain/exceptions/rate_limit_error_exception.py new file mode 100644 index 0000000..9e2ab24 --- /dev/null +++ b/src/application/domain/exceptions/rate_limit_error_exception.py @@ -0,0 +1,8 @@ +from src.application.domain.exceptions.application_exceptions import ApplicationException + +from typing import Mapping + + +class RateLimitErrorException(ApplicationException): + def __init__(self, message: str = 'Rate limit error occurred', headers: Mapping[str, str] | None = None): + super().__init__(500, message, headers) \ No newline at end of file diff --git a/src/infrastructure/database/repositories/user_repository.py b/src/infrastructure/database/repositories/user_repository.py index b28543b..462b12d 100644 --- a/src/infrastructure/database/repositories/user_repository.py +++ b/src/infrastructure/database/repositories/user_repository.py @@ -3,7 +3,7 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.exc import SQLAlchemyError from src.application.contracts import ILogger -from src.application.domain.exceptions import ApplicationException, BadRequestException, InternalException, NotFoundException +from src.application.domain.exceptions import ApplicationException, BadRequestException, DataBaseErrorException, NotFoundException from src.application.abstractions.repositories import IUserRepository from src.application.domain.entities import UserEntity from src.infrastructure.database.models import UserModel @@ -60,7 +60,7 @@ class UserRepository(IUserRepository): raise except SQLAlchemyError as exception: self._logger.exception(str(exception)) - raise InternalException(message=f'Database error: {str(exception)}') + raise DataBaseErrorException(message=f'Database error: {str(exception)}') async def _update_field(self, user_id: str, **fields: object) -> UserEntity: try: @@ -74,7 +74,7 @@ class UserRepository(IUserRepository): raise except SQLAlchemyError as exception: self._logger.exception(str(exception)) - raise InternalException(message=f'Database error: {str(exception)}') + raise DataBaseErrorException(message=f'Database error: {str(exception)}') async def set_phone(self, user_id: str, phone: str) -> UserEntity: return await self._update_field(user_id, phone=phone) @@ -100,7 +100,7 @@ class UserRepository(IUserRepository): raise except SQLAlchemyError as exception: self._logger.exception(str(exception)) - raise InternalException(message=f'Database error: {str(exception)}') + raise DataBaseErrorException(message=f'Database error: {str(exception)}') async def set_password(self, user_id: str, password_hash: str) -> UserEntity: return await self._update_field(user_id, password_hash=password_hash) @@ -121,7 +121,7 @@ class UserRepository(IUserRepository): return result.scalar_one_or_none() is not None except SQLAlchemyError as exception: self._logger.exception(str(exception)) - raise InternalException(message=f'Database error: {str(exception)}') + raise DataBaseErrorException(message=f'Database error: {str(exception)}') async def get_user_by_email(self, email: str) -> UserEntity | None: try: @@ -139,4 +139,4 @@ class UserRepository(IUserRepository): return self._to_entity(user) except SQLAlchemyError as exception: self._logger.exception(str(exception)) - raise InternalException(message=f'Database error: {str(exception)}') + raise DataBaseErrorException(message=f'Database error: {str(exception)}') diff --git a/src/infrastructure/security/jwt.py b/src/infrastructure/security/jwt.py index 7102708..4a1be90 100644 --- a/src/infrastructure/security/jwt.py +++ b/src/infrastructure/security/jwt.py @@ -2,7 +2,7 @@ from __future__ import annotations from jose import jwt, ExpiredSignatureError, JWTError from src.application.contracts import ILogger, IJwtService from src.application.domain.dto import AccessTokenPayload -from src.application.domain.exceptions import ApplicationException, UnauthorizedException, InternalException +from src.application.domain.exceptions import ApplicationException, UnauthorizedException, JwtErrorException from src.infrastructure.config.settings import settings from src.infrastructure.vault import JwtKeyStore @@ -106,4 +106,4 @@ class JwtService(IJwtService): except Exception as exception: self._logger.error(f'Unexpected JWT decode error kid={kid} error={str(exception)}') - raise InternalException(message='JWT decode failed') \ No newline at end of file + raise JwtErrorException(message='JWT decode failed') \ No newline at end of file diff --git a/src/infrastructure/vault/keys.py b/src/infrastructure/vault/keys.py index 9da61da..17cd72b 100644 --- a/src/infrastructure/vault/keys.py +++ b/src/infrastructure/vault/keys.py @@ -2,7 +2,7 @@ from __future__ import annotations import asyncio from datetime import datetime, timezone from src.application.domain.dto import JwtPublicKeySet, JwtPublicKey -from src.application.domain.exceptions import InternalException +from src.application.domain.exceptions import JwtErrorException from src.infrastructure.vault.client import VaultClient @@ -52,7 +52,7 @@ class JwtKeyStore: @classmethod def get_instance(cls) -> 'JwtKeyStore': if cls._instance is None: - raise InternalException(message='JwtKeyStore not initialized') + raise JwtErrorException(message='JwtKeyStore not initialized') return cls._instance def _read_keyset_sync(self) -> JwtPublicKeySet: diff --git a/src/presentation/decorators/rate_limit.py b/src/presentation/decorators/rate_limit.py index 964552b..2de8580 100644 --- a/src/presentation/decorators/rate_limit.py +++ b/src/presentation/decorators/rate_limit.py @@ -7,7 +7,7 @@ from fastapi import Request from redis.asyncio.client import Redis from src.application.contracts import ILogger from src.application.domain.exceptions import ( - InternalException, + RateLimitErrorException, ServiceUnavailableException, TooManyRequestsException, ) @@ -128,7 +128,7 @@ def rate_limit( ident = _call_key_builder(key_builder, request, args, kwargs) # type: ignore[arg-type] except Exception as e: logger.error(f'RateLimit key_builder failed error={str(e)}') - raise InternalException(message='Rate limiter key_builder failed') + raise RateLimitErrorException(message='Rate limiter key_builder failed') route = request.url.path method = request.method