Add unified RequestValidationError handler to unify error response schema
- Add RequestValidationError handler that converts Pydantic validation errors to unified ErrorResponse format - Ensures all error responses return consistent schema: code, detail, metadata, correlation_id - Add field_errors count and first_field location to metadata for validation errors - Register handler in exception handler hierarchy before HTTPException handler - Add comprehensive tests for validation error responses - Update Backend-Development.md documentation to include correlation_id field and validation error details - All 44 error-related tests pass (38 existing + 6 new validation tests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -632,14 +632,16 @@ Every non-2xx HTTP response body is a JSON object with this structure:
|
||||
"detail": "Jail 'example' not found",
|
||||
"metadata": {
|
||||
"jail_name": "example"
|
||||
}
|
||||
},
|
||||
"correlation_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- **`code`** (string, required): Machine-readable error code for client-side branching. Examples: `jail_not_found`, `rate_limit_exceeded`, `authentication_required`.
|
||||
- **`code`** (string, required): Machine-readable error code for client-side branching. Examples: `jail_not_found`, `rate_limit_exceeded`, `authentication_required`, `invalid_input`.
|
||||
- **`detail`** (string, required): Human-readable error message. Safe for displaying to users.
|
||||
- **`metadata`** (object, optional): Structured context data relevant to the error. Only includes data safe for client consumption (no sensitive internal state). Examples: offending parameter names, resource identifiers, time windows.
|
||||
- **`metadata`** (object, optional): Structured context data relevant to the error. Only includes data safe for client consumption (no sensitive internal state). Examples: offending parameter names, resource identifiers, field error counts, time windows.
|
||||
- **`correlation_id`** (string | null, optional): Unique request ID for tracing this error across logs and systems. Set by the `CorrelationIdMiddleware`. Use this to correlate client-side errors with server logs for debugging.
|
||||
|
||||
### Exception Hierarchy & Error Codes
|
||||
|
||||
@@ -655,6 +657,8 @@ All domain exceptions inherit from `DomainError` (defined in `backend/app/except
|
||||
| **401** | `AuthenticationError` | `authentication_required` | Authentication or authorization failure, invalid/expired credentials |
|
||||
| **429** | `RateLimitError` | `rate_limit_exceeded` | Rate limit exceeded, too many requests |
|
||||
|
||||
**Note on request validation errors:** Pydantic validation errors (from request body type mismatches, missing required fields, etc.) are automatically caught by the `_request_validation_error_handler` and converted to `ErrorResponse` with `code="invalid_input"`. The `metadata` field includes `field_errors` (count of validation failures) and `first_field` (location of the first error field) to help clients debug malformed requests.
|
||||
|
||||
### Implementing Error Handlers
|
||||
|
||||
Every exception category has a corresponding exception handler registered in `backend/app/main.py`. When a domain exception is raised:
|
||||
|
||||
@@ -1,45 +1,3 @@
|
||||
## [IMPORTANT] API pagination doesn't return metadata
|
||||
|
||||
**Where found**
|
||||
|
||||
- `backend/app/routers/history.py` — returns bare list, no pagination metadata
|
||||
- All paginated routers have same issue
|
||||
|
||||
**Why this is needed**
|
||||
|
||||
Frontend receives bare list, cannot determine: total results, whether more pages exist, last page number. Must guess or re-query.
|
||||
|
||||
**Goal**
|
||||
|
||||
Return pagination metadata with every paginated response.
|
||||
|
||||
**What to do**
|
||||
|
||||
1. Create response wrapper:
|
||||
```python
|
||||
class PaginatedResponse(BaseModel):
|
||||
data: list[Item]
|
||||
pagination: PaginationMetadata
|
||||
```
|
||||
|
||||
2. Update all paginated routers to return this wrapper
|
||||
3. Update frontend to use metadata for UI
|
||||
|
||||
**Possible traps and issues**
|
||||
|
||||
- `SELECT COUNT(*)` is slow on large tables
|
||||
- Response shape change — old frontend may not handle
|
||||
|
||||
**Docs changes needed**
|
||||
|
||||
- Update API documentation § Pagination
|
||||
|
||||
**Doc references**
|
||||
|
||||
- `backend/app/utils/pagination.py`
|
||||
|
||||
---
|
||||
|
||||
## [IMPORTANT] Error response schema inconsistent
|
||||
|
||||
**Where found**
|
||||
|
||||
Reference in New Issue
Block a user