Skip to content

Commit 31a7cb6

Browse files
committed
refactor: error handling with model
1 parent e9cd98b commit 31a7cb6

4 files changed

Lines changed: 45 additions & 20 deletions

File tree

api/errorhandling/errors.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from dataclasses import asdict, dataclass
2+
from typing import Optional
3+
4+
from fastapi import HTTPException
5+
6+
7+
@dataclass(frozen=True)
8+
class ErrorResponse:
9+
status_code: int
10+
text: str
11+
solution: Optional[str] = None
12+
additional_info: Optional[str] = None
13+
14+
def json(self) -> dict[str, str | int]:
15+
"""Generate a JSON instance from the error content.
16+
17+
Returns:
18+
dict[str, str | int]: JSON representation of the error
19+
"""
20+
return asdict(self)
21+
22+
def as_http_exception(self) -> HTTPException:
23+
"""Convert the error response into an HTTPException.
24+
25+
Returns:
26+
HTTPException: Raisable HTTP exception with details
27+
"""
28+
return HTTPException(status_code=self.status_code, detail=self.json())

api/routes/v0/fizzbuzz.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
import logging
44
import os
55
from http import HTTPStatus
6-
from typing import Optional, Tuple
6+
from typing import Tuple
77

8-
from fastapi import APIRouter, HTTPException
8+
from fastapi import APIRouter
99
from redis import Redis
1010

1111
from api.core.computation import generate_fizzbuzz_sequence
1212
from api.core.constants import EnvironmentVariables, RedisConfigs
1313
from api.core.models import FizzBuzzSequence
14+
from api.errorhandling.errors import ErrorResponse
1415

1516
logger = logging.getLogger(__name__)
1617
redis_client = Redis(
@@ -44,19 +45,16 @@ def __validate_number_input(value: int) -> Tuple[bool, str]:
4445
@router_v0.get("/fizzbuzz")
4546
def compute(number: int) -> FizzBuzzSequence:
4647
"""Compute the fizzbuzz sequence until the given number."""
47-
try:
48-
is_valid, error_message = __validate_number_input(number)
49-
if not is_valid:
50-
raise HTTPException(
51-
status_code=HTTPStatus.BAD_REQUEST,
52-
detail={"error": error_message},
53-
)
54-
except HTTPException as http_err:
55-
logger.error(f"Invalid input: {http_err.detail}")
56-
raise HTTPException(
57-
status_code=http_err.status_code,
58-
detail={"error": f"Invalid input: {http_err.detail}"},
48+
49+
is_valid, error_message = __validate_number_input(number)
50+
if not is_valid:
51+
logger.error(f"Invalid input: {error_message}")
52+
error_data = ErrorResponse(
53+
status_code=HTTPStatus.BAD_REQUEST,
54+
text=error_message,
55+
solution="Please provide a valid 'number' query parameter and try again",
5956
)
57+
raise error_data.as_http_exception()
6058

6159
cache_key = f"fizzbuzz:{number}"
6260
if __should_use_cache():

justfile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ clean:
4747
pytest *args:
4848
@echo "Running unittest suite..."
4949
poetry run pytest {{ args }}
50-
-@find ./ -name '__pycache__' -exec rm -rf {} \;
51-
-@rm -rf .pytest_cache
52-
@echo "Cleaned up test environment"
5350

5451
coverage:
5552
@poetry run coverage run -m pytest

tests/test_api.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ def test_compute_endpoint_valid_input(client: TestClient):
6060
@pytest.mark.api
6161
def test_compute_endpoint_no_number(client: TestClient):
6262
response = client.get("/v0/fizzbuzz")
63-
assert response.status_code == 400
64-
assert "no number provided" in response.json()["message"]
63+
assert response.status_code == 422
6564

6665

6766
@pytest.mark.api
@@ -76,7 +75,10 @@ def test_compute_endpoint_no_number(client: TestClient):
7675
def test_compute_endpoint_invalid_number(client: TestClient, value: int, error: str):
7776
response = client.get(f"/v0/fizzbuzz?number={value}")
7877
assert response.status_code == 400
79-
assert error in response.json()["message"]
78+
error_detail = response.json()["message"]
79+
assert (
80+
error in error_detail["text"]
81+
), f"Expected error message to contain '{error_detail}'"
8082

8183

8284
@pytest.mark.api

0 commit comments

Comments
 (0)