FastAPIでPOSTリクエストを投げた際、なぜか422 Unprocessable Entityエラーが返ってくる現象に遭遇した。DBへのデータ投入や処理の前にバリデーションエラーで弾かれてしまうと、開発が止まってしまいがちだ。
このエラー、実はFastAPIのPydanticモデルがクライアントから送られてきたJSONを正しく解釈できなかった場合に発生する。原因を特定するのが意外と面倒で、ハマると時間を食うので、現場で役立つ解決策をまとめることにした。
※この記事は、個人技術ブログ CodeArchPedia.com の技術メモ(要約)です。
何が起きたか(課題)
FastAPIのPOSTエンドポイントで、期待通りのJSONを送信しているにもかかわらず、サーバー側で422エラーが返却された。具体的な問題点は以下の通りだった。
- リクエストボディのフィールド名が、Pydanticモデルの定義と一致していなかった(特にキャメルケースとスネークケースの混同)。
- 必須フィールドが欠落している、またはデータ型が期待値と異なっていた(例:文字列が送られるべきところに数値が入るなど)。
- エラーレスポンスが詳細すぎて、フロントエンドでのハンドリングがしづらい。
どう解決したか(概要)
解決の基本方針は、FastAPIが自動で返すエラーレスポンスのdetail情報を徹底的に読み解くことだった。これにより、どのフィールドの、どのような型の不整合が発生しているのかを正確に把握できる。
具体的には、以下のステップで対応を進めた。
- エラーレスポンスの
loc(場所)とmsg(内容)を確認し、Pydanticモデル定義とリクエストJSONを突合した。 - フロントエンドで
camelCaseを使っている場合、PydanticのField(alias='...')を使用してAPIの口(JSONキー)と内部の変数名(Pythonの変数名)を分離した。 - 開発者向けの詳細エラーではなく、API利用者に分かりやすいシンプルなエラーを返すため、
RequestValidationErrorを捕捉してカスタムハンドラーを実装した。
例えば、以下のようなPydanticモデルでエラーが発生した場合、
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
"price": "1000"のような文字列を送ると、エラーメッセージからpriceフィールドでfloat型エラーが発生していることが特定できる。
422エラーの主要な原因
FastAPIはPydanticモデルに基づいて自動でバリデーションを実行する。原因は主に以下の3つに分類される。
- Pydanticモデルとの不整合: 必須フィールドの欠落、データ型の不一致、フィールド名のタイポ。
-
不正なリクエスト形式:
Content-Type: application/jsonヘッダーの設定漏れや、JSON自体の構文エラー。
FastAPIのエラー詳細を活用したデバッグ
エラー発生時のレスポンス構造がデバッグの鍵を握る。例えば、数値のフィールドに文字列を送った場合、以下のようなレスポンスが返ってくる。
{
"detail": [
{
"loc": [
"body",
"price"
],
"msg": "value is not a valid float",
"type": "type_error.float"
}
]
}
この情報から、エラーがリクエストボディのpriceフィールドで発生しており、float型として不正であると即座に判断できた。
| エラー原因 | 不正なリクエスト例 (JSON) | 正しいリクエスト例 (JSON) |
|---|---|---|
| 必須フィールドの欠落 | { "name": "My Item" } |
{ "name": "My Item", "price": 9.99 } |
| データ型の不一致 | { "name": "My Item", "price": "9.99" } |
{ "name": "My Item", "price": 9.99 } |
| フィールド名のタイポ | { "Name": "My Item", "price": 9.99 } |
{ "name": "My Item", "price": 9.99 } |
現場で役立つ実践的なTips
エイリアスを使ったキャメルケース対応
フロントエンドとの連携でcamelCaseを受け入れるため、Pydanticのエイリアス機能を利用した。これにより、クライアントからの{"userId": 1}というリクエストを、Pythonコード内ではuser.user_idとして扱えるようになる。
RequestValidationErrorのカスタム
デフォルトのエラーレスポンスを、API利用者が扱いやすい形に整形した。エラーロケーションをドット区切りにし、メッセージを簡潔にまとめることで、フロントエンドのバリデーションハンドリングが容易になった。
効果(Before/After)
この一連の対応を行った結果、バリデーション起因のエラー特定時間が大幅に短縮された。以前は原因不明のままコードを修正していたのが、今ではエラーメッセージを元に数分で特定可能になった。特にエイリアス設定とカスタムエラーハンドリングにより、フロントエンドとの認識齟齬がほぼ解消されたのが大きな成果だ。
🚀 詳細な設定とコードはこちら
具体的なWAFのルール設定や、より詳細なログ解析データは元のブログで公開しています。