バリデーションとエラー設計
🐱 この章の目次
Pydantic による入力バリデーション
FastAPI は Pydantic モデルを通じて入力値を自動バリデーションします。
型が合わない場合やフィールド制約に違反した場合、422 Unprocessable Entity が返ります。
カスタムバリデータを @field_validator で定義すれば、ビジネスルールもスキーマ層で表現できます。
from pydantic import BaseModel, field_validator
class OrderCreate(BaseModel):
quantity: int
price: float
@field_validator("quantity")
@classmethod
def quantity_must_be_positive(cls, v: int) -> int:
if v <= 0:
raise ValueError("quantity は 1 以上にしてください")
return v
RFC 7807 Problem Details
RFC 7807(Problem Details for HTTP APIs) は、エラーレスポンスの構造を標準化する仕様です。
type・title・status・detail・instance のフィールドでエラー情報を表現します。
統一されたフォーマットにより、クライアント側のエラーハンドリングを一貫して実装できます。
{
"type": "https://example.com/errors/validation",
"title": "Validation Error",
"status": 422,
"detail": "quantity は 1 以上にしてください",
"instance": "/orders",
"errors": [
{
"field": "quantity",
"message": "quantity は 1 以上にしてください"
}
]
}
カスタム例外ハンドラ
FastAPI では @app.exception_handler で例外クラスごとにレスポンスをカスタマイズできます。
アプリケーション全体で RFC 7807 形式のエラーを返すには、共通ハンドラを定義します。
from fastapi import Request
from fastapi.responses import JSONResponse
class AppError(Exception):
def __init__(self, status: int, detail: str, error_type: str):
self.status = status
self.detail = detail
self.error_type = error_type
@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
return JSONResponse(
status_code=exc.status,
content={
"type": exc.error_type,
"title": "Application Error",
"status": exc.status,
"detail": exc.detail,
"instance": str(request.url.path),
},
)
バリデーションエラーのカスタマイズ
FastAPI デフォルトの 422 レスポンスは独自形式ですが、RequestValidationError のハンドラを上書きすることで RFC 7807 形式に統一できます。
エラーの国際化やフィールド名のマッピングもこのハンドラ内で行います。
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_error_handler(request: Request, exc: RequestValidationError):
errors = [
{"field": ".".join(str(l) for l in e["loc"][1:]), "message": e["msg"]}
for e in exc.errors()
]
return JSONResponse(
status_code=422,
content={
"type": "https://example.com/errors/validation",
"title": "Validation Error",
"status": 422,
"detail": "入力値に問題があります",
"instance": str(request.url.path),
"errors": errors,
},
)