APIエラー処理の設計について

python

APIを設計する際、エラー処理は非常に重要な要素です。適切なエラー処理により、クライアントは発生した問題を迅速に理解し、対処することができます。本記事では、APIエラーのレスポンス形式について検討し、実際の設計方法を紹介します。

エラー処理の重要性

APIを利用するクライアントアプリケーションは、さまざまな理由でエラーに遭遇します。例えば、リクエストパラメータの不備、認証の失敗、リソースの不足などです。これらのエラーが発生した際に、明確で詳細なエラーメッセージを提供することで、開発者は問題の原因を迅速に特定し、修正することができます。

エラー処理を統一された形式で行うことは、特に複数のクライアントやサービスが関与する場合に有用です。ここで登場するのが、HTTP APIにおけるエラーレスポンスの標準フォーマットを定義するための仕様であるRFC9457です。

RFC9457とは

RFC9457(旧RFC7807)は、HTTP APIにおけるエラーレスポンスの標準フォーマットを定義するための仕様です。この仕様は、エラーが発生した際にクライアントが問題の詳細を理解しやすくするための統一されたレスポンス形式を提供します。

RFC9457の背景

RFC9457は、APIエラーの標準化されたレスポンスフォーマットを提供することで、異なるAPI間での一貫性を保つことを目的としています。これにより、開発者はエラーハンドリングのロジックを再利用しやすくなり、APIの利用体験が向上します。

RFC9457のレスポンス形式

RFC9457の基本的なレスポンス形式は以下の通りです。

{
  "type": "<https://example.com/probs/out-of-credit>",
  "title": "You do not have enough credit.",
  "status": 403,
  "detail": "Your current balance is 30, but that costs 50.",
  "instance": "/account/12345/msgs/abc"
}

各フィールドの意味は以下の通りです:

  • type: エラーの詳細が記述されたページのURL。
  • title: 問題の簡潔な説明。
  • status: 問題に対応するHTTPステータスコード。
  • detail: 問題の詳細な説明。
  • instance: 問題が発生した具体的なリクエストのURI。

エラー時のレスポンス形式

RFC9457をベースにしつつ、開発するAPIに適した形に整形したレスポンス形式は以下の通りです。

{
  "status": 400,
  "title": "無効なリクエストパラメーター",
  "detail": "有効なメールアドレスではありません"
}

各フィールドの説明

  • status: 問題に対応するHTTPステータスコードを示します。ここでは400(Bad Request)を使用しています。
  • title: 問題の簡潔な説明を提供します。同じエラータイプであれば一貫した文言を使用する定型文です。
  • detail: 問題の詳細な説明を行います。エラーが発生するたびに異なる具体的な情報を含みます。

整形した理由

RFC9457のレスポンス形式から不要なフィールドを除外し、シンプルで実用的な形に整形しました。その理由は以下の通りです:

  • type: エラーの詳細が記述されたページのURLを示しますが、現時点ではドキュメントの準備がないため除外しました。
  • instance: 問題が発生した具体的なリクエストのURIを示しますが、使用機会が想定できないため除外しました。
  • extension members: 必要に応じてカスタムフィールドを追加する機能ですが、使用機会が想定できないため除外しました。

これにより、エラーレスポンスがシンプルで分かりやすくなり、クライアントに対して明確なエラー情報を提供できるようになります。

具体的な実装例

次に、PythonとFastAPIを用いた具体的なAPIエラー処理の実装例を示します。

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, EmailStr, ValidationError

app = FastAPI()

class User(BaseModel):
    email: EmailStr

@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "status": exc.status_code,
            "title": exc.detail,
            "detail": "有効なメールアドレスではありません" if exc.status_code == 400 else exc.detail,
        },
    )

@app.post("/api/v1/users")
async def create_user(user: User):
    # ユーザー作成処理
    return {"message": "ユーザーが作成されました"}

@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
    return JSONResponse(
        status_code=400,
        content={
            "status": 400,
            "title": "無効なリクエストパラメーター",
            "detail": exc.errors(),
        },
    )

@app.get("/")
async def root():
    return {"message": "Hello World"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

まとめ

本設計は、RFC9457(旧RFC7807)に基づいて作成しました。RFC9457のレスポンス形式から不要なフィールドを除外し、シンプルで実用的な形に整形しました。これにより、クライアントに対してより明確で理解しやすいエラーメッセージを提供できます。

参考資料

本記事の作成にあたり、以下の資料を参考にしました。

コメント

タイトルとURLをコピーしました