Skip to content

Implement Uniform Error Handling & Response Structure #22

@aniebietafia

Description

@aniebietafia

Problem
The FluentMeet API currently has no centralized error handling strategy. Errors from different layers of the application (validation, authentication, business logic, unhandled exceptions) return inconsistent response shapes, making it difficult for frontend clients to parse and display error messages reliably. This inconsistency complicates debugging, increases client-side boilerplate, and provides a poor developer experience.

Proposed Solution
Implement a uniform error handling system by defining a standard error response schema, a hierarchy of custom application exceptions, and a set of global exception handlers registered with FastAPI. All API errors—whether from request validation, custom business logic, or unexpected failures—will be caught and transformed into a single, predictable JSON structure before being returned to the client.

Standard Error Response Format

{
  "status": "error",
  "code": "ERROR_CODE",
  "message": "Human-readable error message",
  "details": []
}

User Stories

  • As a frontend developer, I want every API error response to follow the same JSON structure, so I can build a single, reusable error-handling utility on the client side.
  • As a backend developer, I want to throw descriptive custom exceptions from anywhere in the codebase and have them automatically formatted into a standard response, so I don't have to manually construct error responses in every endpoint.
  • As a DevOps engineer, I want unhandled exceptions to be logged with full tracebacks on the server while returning only a generic error message to the client, so sensitive internal details are never leaked.

Acceptance Criteria

  1. A base FluentMeetException class is defined in app/core/exceptions.py, along with subclasses for common HTTP error scenarios (BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, InternalServerException).
  2. A standard ErrorResponse Pydantic model is defined in app/core/error_responses.py with fields: status, code, message, and details.
  3. Global exception handlers are implemented in app/core/exception_handlers.py to catch:
    • FluentMeetException → returns the exception's status code, error code, message, and details.
    • RequestValidationError → returns 400 Bad Request with field-specific error details.
    • HTTPException → wraps FastAPI/Starlette HTTP exceptions in the standard format.
    • Unhandled Exception → returns 500 Internal Server Error with a generic message and logs the traceback server-side.
  4. All exception handlers are registered in app/main.py via a register_exception_handlers(app) call.
  5. The following HTTP status codes are used consistently:
    • 400 Bad Request: Validation or business logic errors.
    • 401 Unauthorized: Authentication issues.
    • 403 Forbidden: Permission issues or soft-deleted user access.
    • 404 Not Found: Resource does not exist.
    • 409 Conflict: Duplicate resource (e.g., email already registered).
    • 500 Internal Server Error: Unexpected server failures.
  6. Existing endpoints (e.g., the health check) remain unaffected.
  7. Unit tests verify the correct status code and JSON shape for every handler.

Proposed Technical Details

  • app/core/exceptions.py: Define FluentMeetException(Exception) with status_code, code, message, and details attributes. Create subclasses that pre-set status_code and a default code string (e.g., NotFoundException defaults to 404 and "NOT_FOUND").
  • app/core/error_responses.py: Define ErrorDetail(BaseModel) with optional field and required message, and ErrorResponse(BaseModel) with status="error", code, message, and details: list[ErrorDetail]. Include a create_error_response() helper that returns a JSONResponse.
  • app/core/exception_handlers.py: Implement four handler functions and a register_exception_handlers(app) function that calls app.add_exception_handler(...) for each.
  • app/main.py: Import and call register_exception_handlers(app) after the FastAPI() instance is created.
  • tests/test_error_handling.py: Use TestClient with temporary route injection to trigger each exception type and assert on the response status code and JSON body.

Tasks

  • Create app/core/exceptions.py with FluentMeetException and all subclasses.
  • Create app/core/error_responses.py with ErrorResponse, ErrorDetail, and the create_error_response() helper.
  • Create app/core/exception_handlers.py with all four global handlers and the register_exception_handlers() function.
  • Update app/main.py to import and call register_exception_handlers(app).
  • Create tests/test_error_handling.py with tests for every exception handler.
  • Run the full test suite and verify all tests pass.

Open Questions/Considerations

  • Should the details field in the error response include a request/correlation ID for tracing purposes?
  • Should rate-limiting errors (429 Too Many Requests) be included in the custom exception hierarchy now or deferred to a later issue?
  • For validation errors, should the field path use dot notation (e.g., body.email) or just the field name (e.g., email)?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions