diff --git a/backend/src/modules/api_keys/routes.py b/backend/src/modules/api_keys/routes.py index d94f0398..501212bc 100644 --- a/backend/src/modules/api_keys/routes.py +++ b/backend/src/modules/api_keys/routes.py @@ -2,15 +2,10 @@ from typing import Any -from fastapi import APIRouter, HTTPException, Path, Query, status +from fastapi import APIRouter, Path, Query from fastcrud import PaginatedListResponse, compute_offset, paginated_response from ...infrastructure.dependencies import AsyncSessionDep, CurrentUserDep -from ..common.exceptions import ( - PermissionDeniedError, - ResourceNotFoundError, -) -from ..common.utils.error_handler import handle_exception from .dependencies import APIKeyServiceDep from .schemas import ( APIKeyCreate, @@ -54,17 +49,11 @@ async def create_api_key( db: AsyncSessionDep, ) -> dict[str, Any]: """Create a new API key for the authenticated user.""" - try: - return await api_key_service.create_api_key( - user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, - key_data=key_data, - db=db, - ) - except Exception as e: - http_exc = handle_exception(e) - if http_exc: - raise http_exc - raise HTTPException(status_code=500, detail="Internal server error") + return await api_key_service.create_api_key( + user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, + key_data=key_data, + db=db, + ) @router.get( @@ -96,25 +85,19 @@ async def get_user_api_keys( items_per_page: int = Query(50, ge=1, le=100, description="Items per page"), ) -> dict[str, Any]: """Get all API keys for the authenticated user.""" - try: - result = await api_key_service.get_user_api_keys( - user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, - active_only=active_only, - limit=items_per_page, - offset=compute_offset(page, items_per_page), - db=db, - ) - - return paginated_response( - crud_data=result, - page=page, - items_per_page=items_per_page, - ) - except Exception as e: - http_exc = handle_exception(e) - if http_exc: - raise http_exc - raise HTTPException(status_code=500, detail="Internal server error") + result = await api_key_service.get_user_api_keys( + user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, + active_only=active_only, + limit=items_per_page, + offset=compute_offset(page, items_per_page), + db=db, + ) + + return paginated_response( + crud_data=result, + page=page, + items_per_page=items_per_page, + ) @router.get( @@ -143,21 +126,11 @@ async def get_api_key( key_id: int = Path(..., description="API key ID"), ) -> dict[str, Any]: """Get details for a specific API key.""" - try: - return await api_key_service.get_api_key( - key_id=key_id, - user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, - db=db, - ) - except ResourceNotFoundError as e: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) - except PermissionDeniedError as e: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e)) - except Exception as e: - http_exc = handle_exception(e) - if http_exc: - raise http_exc - raise HTTPException(status_code=500, detail="Internal server error") + return await api_key_service.get_api_key( + key_id=key_id, + user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, + db=db, + ) @router.patch( @@ -188,22 +161,12 @@ async def update_api_key( key_id: int = Path(..., description="API key ID"), ) -> dict[str, Any]: """Update an existing API key.""" - try: - return await api_key_service.update_api_key( - key_id=key_id, - user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, - update_data=update_data, - db=db, - ) - except ResourceNotFoundError as e: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) - except PermissionDeniedError as e: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e)) - except Exception as e: - http_exc = handle_exception(e) - if http_exc: - raise http_exc - raise HTTPException(status_code=500, detail="Internal server error") + return await api_key_service.update_api_key( + key_id=key_id, + user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, + update_data=update_data, + db=db, + ) @router.delete( @@ -234,21 +197,11 @@ async def delete_api_key( key_id: int = Path(..., description="API key ID"), ) -> None: """Delete (deactivate) an API key.""" - try: - await api_key_service.delete_api_key( - key_id=key_id, - user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, - db=db, - ) - except ResourceNotFoundError as e: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) - except PermissionDeniedError as e: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e)) - except Exception as e: - http_exc = handle_exception(e) - if http_exc: - raise http_exc - raise HTTPException(status_code=500, detail="Internal server error") + await api_key_service.delete_api_key( + key_id=key_id, + user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, + db=db, + ) @router.get( @@ -282,29 +235,19 @@ async def get_key_usage( items_per_page: int = Query(100, ge=1, le=1000, description="Items per page"), ) -> dict[str, Any]: """Get usage history for an API key.""" - try: - result = await api_key_service.get_key_usage( - key_id=key_id, - user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, - limit=items_per_page, - offset=compute_offset(page, items_per_page), - db=db, - ) - - return paginated_response( - crud_data=result, - page=page, - items_per_page=items_per_page, - ) - except ResourceNotFoundError as e: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) - except PermissionDeniedError as e: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e)) - except Exception as e: - http_exc = handle_exception(e) - if http_exc: - raise http_exc - raise HTTPException(status_code=500, detail="Internal server error") + result = await api_key_service.get_key_usage( + key_id=key_id, + user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, + limit=items_per_page, + offset=compute_offset(page, items_per_page), + db=db, + ) + + return paginated_response( + crud_data=result, + page=page, + items_per_page=items_per_page, + ) @router.get( @@ -339,22 +282,12 @@ async def get_key_analytics( days: int = Query(30, ge=1, le=365, description="Number of days to analyze"), ) -> dict[str, Any]: """Get usage analytics for an API key.""" - try: - return await api_key_service.get_usage_analytics( - key_id=key_id, - user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, - days=days, - db=db, - ) - except ResourceNotFoundError as e: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) - except PermissionDeniedError as e: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e)) - except Exception as e: - http_exc = handle_exception(e) - if http_exc: - raise http_exc - raise HTTPException(status_code=500, detail="Internal server error") + return await api_key_service.get_usage_analytics( + key_id=key_id, + user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, + days=days, + db=db, + ) @router.get( @@ -383,13 +316,7 @@ async def get_user_summary( db: AsyncSessionDep, ) -> dict[str, Any]: """Get comprehensive API key summary for the authenticated user.""" - try: - return await api_key_service.get_user_summary( - user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, - db=db, - ) - except Exception as e: - http_exc = handle_exception(e) - if http_exc: - raise http_exc - raise HTTPException(status_code=500, detail="Internal server error") + return await api_key_service.get_user_summary( + user_id=current_user["id"] if isinstance(current_user, dict) else current_user.id, + db=db, + ) diff --git a/backend/src/modules/rate_limit/routes.py b/backend/src/modules/rate_limit/routes.py index 3556282f..1b3ccbf5 100644 --- a/backend/src/modules/rate_limit/routes.py +++ b/backend/src/modules/rate_limit/routes.py @@ -3,10 +3,7 @@ from fastapi import APIRouter from fastcrud import PaginatedListResponse, compute_offset, paginated_response -from ...infrastructure.auth.http_exceptions import DuplicateValueException, HTTPException, NotFoundException from ...infrastructure.dependencies import AsyncSessionDep, CurrentSuperUserDep -from ..common.exceptions import ResourceExistsError, ResourceNotFoundError -from ..common.utils.error_handler import handle_exception from .dependencies import RateLimitServiceDep from .schemas import ( RateLimitRead, @@ -45,19 +42,13 @@ async def get_rate_limits( Get a paginated list of all rate limits. This endpoint is available to all authenticated users. """ - try: - rate_limits_data = await rate_limit_service.get_all( - db=db, - skip=compute_offset(page, items_per_page), - limit=items_per_page, - ) + rate_limits_data = await rate_limit_service.get_all( + db=db, + skip=compute_offset(page, items_per_page), + limit=items_per_page, + ) - return paginated_response(crud_data=rate_limits_data, page=page, items_per_page=items_per_page) - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + return paginated_response(crud_data=rate_limits_data, page=page, items_per_page=items_per_page) @router.get( @@ -88,16 +79,8 @@ async def get_rate_limit( Get detailed information about a specific rate limit by name. This endpoint is available to all authenticated users. """ - try: - rate_limit = await rate_limit_service.get_by_name(name, db) - return rate_limit - except ResourceNotFoundError: - raise NotFoundException("Rate limit not found") - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + rate_limit = await rate_limit_service.get_by_name(name, db) + return rate_limit @router.patch( @@ -139,18 +122,8 @@ async def update_rate_limit( Update an existing rate limit. This endpoint is restricted to superusers only. """ - try: - await rate_limit_service.update(name, values, db) - return {"message": "Rate limit updated"} - except ResourceNotFoundError: - raise NotFoundException("Rate limit not found") - except ResourceExistsError: - raise DuplicateValueException("Rate limit name already exists") - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + await rate_limit_service.update(name, values, db) + return {"message": "Rate limit updated"} @router.delete( @@ -188,13 +161,5 @@ async def delete_rate_limit( Delete a rate limit. This endpoint is restricted to superusers only. """ - try: - await rate_limit_service.delete(name, db) - return {"message": "Rate limit deleted"} - except ResourceNotFoundError: - raise NotFoundException("Rate limit not found") - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + await rate_limit_service.delete(name, db) + return {"message": "Rate limit deleted"} diff --git a/backend/src/modules/tier/routes.py b/backend/src/modules/tier/routes.py index 9613b96c..5988c5dc 100644 --- a/backend/src/modules/tier/routes.py +++ b/backend/src/modules/tier/routes.py @@ -1,12 +1,9 @@ from typing import Any -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter from fastcrud import PaginatedListResponse, compute_offset, paginated_response -from ...infrastructure.auth.http_exceptions import NotFoundException from ...infrastructure.dependencies import AsyncSessionDep -from ..common.exceptions import TierNotFoundError -from ..common.utils.error_handler import handle_exception from .dependencies import TierServiceDep from .schemas import TierRead @@ -21,18 +18,12 @@ async def get_tiers( items_per_page: int = 10, ) -> dict: """Paginated list of tiers.""" - try: - tiers_data = await tier_service.get_all( - db=db, - skip=compute_offset(page, items_per_page), - limit=items_per_page, - ) - return paginated_response(crud_data=tiers_data, page=page, items_per_page=items_per_page) - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + tiers_data = await tier_service.get_all( + db=db, + skip=compute_offset(page, items_per_page), + limit=items_per_page, + ) + return paginated_response(crud_data=tiers_data, page=page, items_per_page=items_per_page) @router.get("/{name}", response_model=TierRead, summary="Get a tier by name") @@ -42,12 +33,4 @@ async def get_tier_by_name( tier_service: TierServiceDep, ) -> dict[str, Any]: """Get a tier by name.""" - try: - return await tier_service.get_by_name(name, db) - except TierNotFoundError: - raise NotFoundException("Tier not found") - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + return await tier_service.get_by_name(name, db) diff --git a/backend/src/modules/user/routes.py b/backend/src/modules/user/routes.py index e9265cf4..84498b9d 100644 --- a/backend/src/modules/user/routes.py +++ b/backend/src/modules/user/routes.py @@ -3,13 +3,11 @@ from fastapi import APIRouter from fastcrud import PaginatedListResponse, compute_offset, paginated_response -from ...infrastructure.auth.http_exceptions import HTTPException from ...infrastructure.dependencies import ( AsyncSessionDep, CurrentSuperUserDep, CurrentUserDep, ) -from ..common.utils.error_handler import handle_exception from .dependencies import UserServiceDep from .schemas import ( UserCreate, @@ -50,13 +48,7 @@ async def create_user( user_service: UserServiceDep, ) -> dict[str, Any]: """Create a new user account.""" - try: - return await user_service.create(user, db) - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + return await user_service.create(user, db) @router.get( @@ -137,16 +129,8 @@ async def get_user_by_username( user_service: UserServiceDep, ) -> dict[str, Any]: """Get user profile by username.""" - try: - user = await user_service.get_by_username(username, db) - if user is None: - raise HTTPException(status_code=404, detail=f"User with username {username} not found") - return user - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + user = await user_service.get_by_username(username, db) + return user @router.get( @@ -176,16 +160,7 @@ async def get_active_and_inactive_user_by_username( user_service: UserServiceDep, ) -> dict[str, Any]: """Get active and inactive profile by username.""" - try: - user = await user_service.get_active_and_inactive_by_username(username, db) - if user is None: - raise HTTPException(status_code=404, detail=f"User with username {username} not found") - return user - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + return await user_service.get_active_and_inactive_by_username(username, db) @router.patch( @@ -222,19 +197,11 @@ async def update_user_profile( user_service: UserServiceDep, ) -> dict[str, str]: """Update user profile information.""" - try: - await user_service.verify_user_permission(current_user, username, "update profile") - user = await user_service.get_by_username(username, db) - if user is None: - raise HTTPException(status_code=404, detail=f"User with username {username} not found") + await user_service.verify_user_permission(current_user, username, "update profile") + user = await user_service.get_by_username(username, db) - await user_service.update(user["id"], values, db) - return {"message": "User updated successfully"} - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + await user_service.update(user["id"], values, db) + return {"message": "User updated successfully"} @router.delete( @@ -268,19 +235,11 @@ async def delete_user_account( user_service: UserServiceDep, ) -> dict[str, str]: """Soft delete a user account.""" - try: - user = await user_service.get_by_username(username, db) - if user is None: - raise HTTPException(status_code=404, detail=f"User with username {username} not found") + await user_service.verify_user_permission(current_user, username, "delete this account") + user = await user_service.get_by_username(username, db) - await user_service.verify_user_permission(current_user, username, "delete this account") - await user_service.delete(user["id"], db) - return {"message": "User account deactivated"} - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + await user_service.delete(user["id"], db) + return {"message": "User account deactivated"} @router.delete( @@ -325,17 +284,9 @@ async def gdpr_delete_user( _: CurrentSuperUserDep, ) -> dict[str, str]: """GDPR compliant user anonymization (admin only).""" - try: - user = await user_service.get_active_and_inactive_by_username(username, db) - if user is None: - raise HTTPException(status_code=404, detail=f"User with username {username} not found") - await user_service.anonymize_user(user["id"], db) - return {"message": "User data anonymized in compliance with GDPR"} - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + user = await user_service.get_active_and_inactive_by_username(username, db) + await user_service.anonymize_user(user["id"], db) + return {"message": "User data anonymized in compliance with GDPR"} @router.get( @@ -369,17 +320,9 @@ async def get_user_rate_limits( user_service: UserServiceDep, ) -> dict[str, Any]: """Get rate limits for a user.""" - try: - await user_service.verify_user_permission(current_user, username, "view rate limits") - user = await user_service.get_by_username(username, db) - if user is None: - raise HTTPException(status_code=404, detail=f"User with username {username} not found") - return await user_service.get_rate_limits(user["id"], db) - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + await user_service.verify_user_permission(current_user, username, "view rate limits") + user = await user_service.get_by_username(username, db) + return await user_service.get_rate_limits(user["id"], db) @router.get( @@ -413,18 +356,10 @@ async def get_user_tier( user_service: UserServiceDep, ) -> dict[str, Any]: """Get detailed tier information for a user.""" - try: - await user_service.verify_user_permission(current_user, username, "view tier information") + await user_service.verify_user_permission(current_user, username, "view tier information") - user = await user_service.get_by_username(username, db) - if user is None: - raise HTTPException(status_code=404, detail=f"User with username {username} not found") - return await user_service.get_user_with_tier(user["id"], db) - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + user = await user_service.get_by_username(username, db) + return await user_service.get_user_with_tier(user["id"], db) @router.patch( @@ -458,14 +393,6 @@ async def update_user_tier( _: CurrentSuperUserDep, ) -> dict[str, str]: """Update a user's subscription tier (admin only).""" - try: - user = await user_service.get_by_username(username, db) - if user is None: - raise HTTPException(status_code=404, detail=f"User with username {username} not found") - await user_service.update_tier(user["id"], values, db) - return {"message": "User tier updated successfully"} - except Exception as e: - http_exception = handle_exception(e) - if http_exception: - raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + user = await user_service.get_by_username(username, db) + await user_service.update_tier(user["id"], values, db) + return {"message": "User tier updated successfully"} diff --git a/backend/tests/integration/api/v1/users/test_delete.py b/backend/tests/integration/api/v1/users/test_delete.py index 9d9b9d8c..a321bb7a 100644 --- a/backend/tests/integration/api/v1/users/test_delete.py +++ b/backend/tests/integration/api/v1/users/test_delete.py @@ -4,6 +4,8 @@ from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession +from src.modules.common.constants import GENERIC_ERROR_MESSAGE + from .test_create import generate_unique_user_data logging.basicConfig(level=logging.INFO) @@ -62,19 +64,17 @@ async def test_soft_delete_wrong_user( assert response.status_code == 403 data = response.json() - assert "permission" in data["detail"].lower() + assert data["detail"] == GENERIC_ERROR_MESSAGE async def test_soft_delete_nonexistent_user( auth_client: AsyncClient, db_session: AsyncSession, ): - """Test soft deletion of non-existent user.""" + """Test that deleting a non-existent user returns 403 (permission is checked first).""" response = await auth_client.delete("/api/v1/users/nonexistentuser") - assert response.status_code == 404 - data = response.json() - assert "not found" in data["detail"].lower() + assert response.status_code == 403 async def test_permanent_delete_success( @@ -147,7 +147,7 @@ async def test_permanent_delete_nonexistent_user( assert response.status_code == 404 data = response.json() - assert "not found" in data["detail"].lower() + assert data["detail"] == GENERIC_ERROR_MESSAGE async def test_delete_cascade_effects( diff --git a/backend/tests/integration/api/v1/users/test_update.py b/backend/tests/integration/api/v1/users/test_update.py index d6ce6337..19a3a34b 100644 --- a/backend/tests/integration/api/v1/users/test_update.py +++ b/backend/tests/integration/api/v1/users/test_update.py @@ -4,6 +4,8 @@ from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession +from src.modules.common.constants import GENERIC_ERROR_MESSAGE + from .test_create import generate_unique_user_data logging.basicConfig(level=logging.INFO) @@ -88,7 +90,7 @@ async def test_update_user_profile_wrong_user( assert response.status_code == 403 data = response.json() - assert "permission" in data["detail"].lower() + assert data["detail"] == GENERIC_ERROR_MESSAGE async def test_update_user_profile_duplicate_email( diff --git a/docs/user-guide/api/exceptions.md b/docs/user-guide/api/exceptions.md index d5b8ea4b..e1b33aff 100644 --- a/docs/user-guide/api/exceptions.md +++ b/docs/user-guide/api/exceptions.md @@ -55,22 +55,21 @@ Re-exported from FastCRUD in `backend/src/infrastructure/auth/http_exceptions.py | `HTTPException` | base FastAPI class | | `CSRFException` | 403 with `X-CSRF-Error: true` header (defined locally) | -Use these from routes when you have an HTTP-shaped failure and no service involvement: +Use these from routes when you have an HTTP-shaped failure and no service involvement (domain errors raised by services need no route-level handling — see the mapping layer below): ```python -from ...infrastructure.auth.http_exceptions import NotFoundException +from ...infrastructure.auth.http_exceptions import BadRequestException -@router.get("/{name}", response_model=TierRead) -async def get_tier_by_name(...): - try: - return await tier_service.get_by_name(name, db) - except TierNotFoundError: - raise NotFoundException("Tier not found") +@router.get("/") +async def search(q: str | None = None): + if q is None: + raise BadRequestException("Provide ?q=") + # ... ``` -## The Mapping Layer +## The Mapping Layer (Centralized) -`modules/common/utils/error_handler.py` ships two ways to bridge domain → HTTP errors: +`modules/common/utils/error_handler.py` bridges domain → HTTP errors globally. ### Global Handler (Automatic) @@ -80,13 +79,26 @@ async def get_tier_by_name(...): - A catch-all `DomainError` handler → maps to the right HTTP status via `EXCEPTION_MAPPING`, returns a **generic** message + `support_id`. The full details are logged server-side. - A `CatchAllErrorMiddleware` that converts truly unhandled exceptions into 500s with a `support_id` -This means: **any uncaught `DomainError` raised in a service automatically becomes a properly-shaped HTTP response.** You don't have to wire it up per-route. +This means: **any uncaught `DomainError` raised in a service automatically becomes a properly-shaped HTTP response.** Routes do *not* wrap service calls in try/except — they just let exceptions propagate: + +```python +@router.post("/", response_model=UserRead, status_code=201) +async def create_user( + user: UserCreate, + db: AsyncSessionDep, + user_service: UserServiceDep, +) -> dict[str, Any]: + return await user_service.create(user, db) +``` + +If the service raises `UserExistsError`, the client gets a 409 with a generic message and a `support_id`; anything unexpected becomes a 500 the same way. -### Manual Handler (Explicit) +### Manual Handler (Rare) -Inside route handlers, you can use `handle_exception()` to translate explicitly. This is the convention in the existing routes — it's slightly more verbose but it keeps the error path obvious in code review: +For cases where a route genuinely needs to intercept an exception itself (e.g. to add context or recover), `handle_exception()` is still available: ```python +from ..common.constants import GENERIC_ERROR_MESSAGE from ..common.utils.error_handler import handle_exception from ...infrastructure.auth.http_exceptions import HTTPException @@ -103,7 +115,7 @@ async def create_user( http_exception = handle_exception(e) if http_exception: raise http_exception - raise HTTPException(status_code=500, detail="An unexpected error occurred") + raise HTTPException(status_code=500, detail=GENERIC_ERROR_MESSAGE) ``` `handle_exception()`: