Files

3.8 KiB

Error Handling

How errors are handled in this project.


Overview

Backend error transport is RFC7807-style application/problem+json with stable code and optional params extensions.

Evidence:

  • Error class: backend/src/core/http/errors.py (ApiProblemError)
  • Problem details builder: backend/src/core/http/response.py
  • Global exception mapping: backend/src/app.py
  • Error code registry: docs/protocols/common/http-error-codes.md

Error Types

1) ApiProblemError (preferred for business/API errors)

Used to carry status_code, detail, code, params.

Examples:

  • backend/src/v1/todo/service.py (_todo_error(...) returns ApiProblemError)
  • backend/src/v1/schedule_items/service.py (raises ApiProblemError with problem_payload(...))
  • backend/src/v1/users/dependencies.py (auth failures mapped to AUTH_UNAUTHORIZED, JWT_VERIFIER_NOT_CONFIGURED)

2) Infra/DB exceptions (caught at boundary and converted)

  • SQLAlchemyError is commonly caught in service/repository boundaries.
  • Many repository methods log and re-raise DB exceptions (for example in todo/schedule_items repositories); generic helpers in core/db/base_repository.py may just propagate exceptions.
  • Service layer usually maps infra failures to stable codes like *_SERVICE_UNAVAILABLE / *_STORE_UNAVAILABLE.

Examples:

  • backend/src/v1/todo/repository.py
  • backend/src/v1/todo/service.py
  • backend/src/v1/schedule_items/service.py

Error Handling Patterns

Observed patterns:

  1. Service methods: catch DB/infrastructure errors, rollback when needed, then raise typed API problem.
    • backend/src/v1/todo/service.py
    • backend/src/v1/schedule_items/service.py
  2. Repository methods: log exception context and re-raise.
    • backend/src/v1/todo/repository.py
    • backend/src/v1/schedule_items/repository.py
  3. Global handlers: convert unhandled errors and framework exceptions into problem+json responses.
    • backend/src/app.py

API Error Responses

Expected transport:

  • Media type: application/problem+json
  • Fields: RFC7807 core (type, title, status, detail, instance) + extension fields (code, params)

Evidence:

  • Response builder model in backend/src/core/http/response.py
  • Exception handlers in backend/src/app.py
  • Registry and compatibility notes in docs/protocols/common/http-error-codes.md

Practical rule:

  • For business branches, return stable UPPER_SNAKE_CASE codes and keep detail human-readable.

Anti-patterns / Forbidden Patterns

  • Do not return free-text-only errors for business branches (missing code).
    • Protocol source: docs/protocols/common/http-error-codes.md
  • Do not swallow exceptions with except ...: log; return default in new code.
    • Project-wide rule from root AGENTS.md: no error swallowing.
  • Do not add new service/repository branches that raise HTTPException directly.
    • Backend target rule in backend/AGENTS.md prefers ApiProblemError in non-router layers.

Existing legacy evidence to be aware of:

  • backend/src/core/db/base_service.py still raises HTTPException(401, "Unauthorized").
  • backend/src/v1/users/dependencies.py has fallback branches returning None after catching exceptions in _verify_user_with_supabase.

These are current-state observations, not patterns to copy for new code.


Uncertainties (documented, not invented)

  • backend/src/core/logging/middleware.py defines register_exception_handlers(...) returning { "detail": "Internal Server Error" } JSON (non-problem+json); main app currently defines its own handlers in backend/src/app.py. The single enforced runtime path should be clarified before broad refactors.
  • There is no automated CI check in this repo yet that verifies every endpoint always includes code in all error branches; compliance is currently convention + review driven.