Files
openapi-first/docs/wiki/06_error_handling.md

122 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Error Handling — Fail Loud, Fail Early
`openapi-first` treats errors as **first-class contract documents**: every failure mode is a named exception with a stable import pathhare, and every one surfaces as early as possible.
---
## 🧬 1. The Hierarchy
All errors derive from `OpenAPIFirstError` (in `openapi_first/errors.py`), so a single `except OpenAPIFirstError` catches every first-party failure:
```text
OpenAPIFirstError
├── OpenAPISpecError # spec-level problems
│ └── OpenAPISpecLoadError # load / parse / validation (loader)
├── OpenAPIClientError # client-side contract issues (client)
└── MissingOperationHandler # spec op with no handler (binder)
# Security / loader layers raise through OpenAPISpecError subclasses too
```
| Exception | Module | Raised when |
|----------------------------------|-----------|-------------|
| `OpenAPISpecLoadError` | `loader` | Path missing, file unreadable, YAML/JSON invalid, or spec fails OpenAPI 3.x validation |
| `OpenAPIClientError` | `client` | No `servers`, no `paths`, missing/duplicate `operationId`, missing required params at construction |
| `MissingOperationHandler` | `errors` | An operation is declared whose `operationId` has no matching handler in `routes_module` |
---
## ⏱️ 2. When Things Fail
The single most important rule: **violations are eager, not lazy.**
### 2.1 At application startup
```python
# openapi.yaml missing ────────────────────────────► OpenAPISpecLoadError
# operationId without a handler ───────────────────► MissingOperationHandler
# operation with no operationId declared ──────────► MissingOperationHandler
```
Because these raise during `OpenAPIFirstApp(...)` construction, CI catches them the moment a spec and its routes drift — before a single request is served.
### 2.2 At client construction
```python
OpenAPIClient(spec) # fails fast, same philosophy
```
- Spec with no `servers``OpenAPIClientError`
- Spec with no `paths``OpenAPIClientError`
- Duplicate `operationId`s → `OpenAPIClientError` (client methods must be unambiguous)
- Operation missing `operationId``OpenAPIClientError`
### 2.3 At call time (client)
Runtime transport errors surface as `httpx` exceptions (`httpx.RequestError` family), not swallowed or remapped. Missing required args fail before any HTTP request is made:
```python
client.get_user(path_params={"user_id": ...}) # OK
client.get_user() # ValueError — user_id required
```
---
## 🧰 3. Handling in Your App
### 3.1 Server-side
Handlers raise FastAPI `HTTPException` for expected operation-level failures (404/422), and the `OperationId`-binding errors only exist at startup:
```python
from fastapi import HTTPException
def get_item(item_id: int):
"""Retrieve an item by ID.
Implements the OpenAPI operation ``get_item``.
Args:
item_id (int): Identifier of the item.
Raises:
HTTPException: If the item does not exist (404).
"""
try:
return _get_item(item_id)
except KeyError:
raise HTTPException(status_code=404, detail="Item not found")
```
### 3.2 Client-side
```python
import httpx
try:
response = client.get_item(path_params={"item_id": 1})
except httpx.HTTPStatusError as exc:
... # 4xx/5xx from the server
```
`httpx.HTTPStatusError` isn't raised by the library — it's the standard `httpx.raise_for_status()` you can opt into per call. The library never masks a response code.
---
## 🛡️ 4. Fail-Fast Guarantees Recap
| Layer | You write | The library guarantees |
|-------|-----------|------------------------|
| Loader | a spec path | unreadable/invalid specs never reach your app |
| Binder | handler functions | every operation must resolve, or the app won't start |
| Client | a spec | every operationId becomes a callable; missing ones fail at construction |
| Runtime | handler code | FastAPI + Pydantic handle coercion; contract checks already happened at startup |
None of these can silently degrade: a violation is an **exception at construction**, not a 500 at request time.
---
## Related
- [01 Overview](01_overview.md) · [05 Security](05_security.md) · [07 Testing](07_testing.md)