Files
openapi-first/docs/wiki/06_error_handling.md
Vishesh 'ironeagle' Bangotra 621f10409e chore(release): 0.0.7
Squash-merge lib-standardisation: adopt repo standards across packaging,
tooling, docs, and CI.

- standardize pyproject.toml (canonical packaging, lint config, extras, GSDFC pydoclint)
- add py.typed marker, modernize type hints and Google-style docstrings
- nest generated docs under docs/lib and docs/mcp; add hand-written wiki
- adopt standard Type A PyPI CI pipeline (tag==version guard + quality gate)
- promote generated CHANGELOG.md; bump version to 0.0.7
2026-10-17 02:16:49 +05:30

4.4 KiB
Raw Permalink Blame History

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:

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

# 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

OpenAPIClient(spec)  # fails fast, same philosophy
  • Spec with no servers → OpenAPIClientError
  • Spec with no paths → OpenAPIClientError
  • Duplicate operationIds → 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:

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:

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

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.