TT Lab
はじめる
学ぶ 学習パス コース

FastAPI — 型がそのまま契約だ

エラー応答もAPIの契約

TT Labで続きを見る

目標

業務例外と内部エラーを区別し、一貫した問題レスポンスを提供します。

なぜ重要なのか

クライアントは、エラーが出ると、detailの文字列を正規表現で読んでいました。ある日、文言が変わると、在庫不足も決済のリトライとして処理されました。別の経路では、例外の文字列がそのまま公開されて、SQLと内部のパスが漏れました。エラーには、成功のレスポンスに劣らず、明示的な契約が必要です。

ステップ

  1. /root/work/fa-problem-lab/service.pyで、DomainError(code, message)は、Exceptionのサブクラスで、.codeにcodeを保持します。str(例外)は、messageです。

最初に一度だけ準備してください。既存のファイルは上書きしません。

mkdir -p /root/work/fa-problem-lab
test -e /root/work/fa-problem-lab/service.py || cp /opt/fixtures/ten_labs/fa-problem-lab/service.py /root/work/fa-problem-lab/service.py
cd /root/work/fa-problem-lab
  1. /root/work/fa-problem-lab/service.pyで、status_for(code)は、missing=404、conflict=409、invalid=422、それ以外=500です。

  2. /root/work/fa-problem-lab/service.pyで、public_message(code)は、missing='Resource not found'、conflict='State conflict'、invalid='Invalid request'、それ以外='Internal error'です。

  3. /root/work/fa-problem-lab/service.pyで、request_id(value)は、ASCIIの英字・数字・アンダースコア・ハイフンだけで構成された1–32文字の文字列ならそのまま、そうでなければ'untracked'です。

  4. /root/work/fa-problem-lab/service.pyで、problem(code, rid)は、type='urn:labhub:problem:'+code、titleとdetail=public_message(code)、status=status_for(code)、request_id=request_id(rid)だけを持つ辞書です。

  5. /root/work/fa-problem-lab/service.pyで、response_for(code, rid)は、problemを本文に、status_forをステータスに、application/problem+jsonをmedia_typeに、X-Request-IDを正規化したridにしたJSONResponseです。

  6. /root/work/fa-problem-lab/service.pyで、install_handlers(app)は、DomainErrorのハンドラーを登録します。ヘッダーX-Request-IDを読んで、exc.codeに対してresponse_forを返します。excのmessageは、レスポンスに入れません。

  7. /root/work/fa-problem-lab/service.pyで、create_app()は、ハンドラーをインストールして、GET /fail/{code}でDomainError(code, 内部の文言)を出します。ただしcode=boomならRuntimeErrorを出します。RuntimeErrorのハンドラーは、code=internalの固定の500の問題レスポンスを返します。

参考

業務例外にcodeを残す

/root/work/fa-problem-lab/service.pyで、DomainError(code, message)は、Exceptionのサブクラスで、.codeにcodeを保持します。str(例外)は、messageです。

最初に一度だけ準備してください。既存のファイルは上書きしません。

mkdir -p /root/work/fa-problem-lab
test -e /root/work/fa-problem-lab/service.py || cp /opt/fixtures/ten_labs/fa-problem-lab/service.py /root/work/fa-problem-lab/service.py
cd /root/work/fa-problem-lab

機械が判断するcodeと、人が見る内部メッセージを分離します。

保存したら、bash /opt/lab/checks/fa-problem-lab/01-contract.shで確認してください。

codeをステータスにマッピングする

/root/work/fa-problem-lab/service.pyで、status_for(code)は、missing=404、conflict=409、invalid=422、それ以外=500です。

未知のcodeを、成功として扱いません。

保存したら、bash /opt/lab/checks/fa-problem-lab/02-contract.shで確認してください。

公開用の文言を固定する

/root/work/fa-problem-lab/service.pyで、public_message(code)は、missing='Resource not found'、conflict='State conflict'、invalid='Invalid request'、それ以外='Internal error'です。

例外の文字列にDBのアドレスや内部のパスが入っていても、公開しません。

保存したら、bash /opt/lab/checks/fa-problem-lab/03-contract.shで確認してください。

リクエストidを制限する

/root/work/fa-problem-lab/service.pyで、request_id(value)は、ASCIIの英字・数字・アンダースコア・ハイフンだけで構成された1–32文字の文字列ならそのまま、そうでなければ'untracked'です。

任意のヘッダーをそのまま返さないように、長さと文字集合を一緒に制限します。

保存したら、bash /opt/lab/checks/fa-problem-lab/04-contract.shで確認してください。

問題の本文を作る

/root/work/fa-problem-lab/service.pyで、problem(code, rid)は、type='urn:labhub:problem:'+code、titleとdetail=public_message(code)、status=status_for(code)、request_id=request_id(rid)だけを持つ辞書です。

本文とHTTPのステータスが食い違うと、クライアントはどちらの値を信じるか決められません。

保存したら、bash /opt/lab/checks/fa-problem-lab/05-contract.shで確認してください。

レスポンスの形式を一貫させる

/root/work/fa-problem-lab/service.pyで、response_for(code, rid)は、problemを本文に、status_forをステータスに、application/problem+jsonをmedia_typeに、X-Request-IDを正規化したridにしたJSONResponseです。

単純な辞書を返すだけでは、エラーも200になってしまうことがあります。

保存したら、bash /opt/lab/checks/fa-problem-lab/06-contract.shで確認してください。

業務例外のハンドラーをつなぐ

/root/work/fa-problem-lab/service.pyで、install_handlers(app)は、DomainErrorのハンドラーを登録します。ヘッダーX-Request-IDを読んで、exc.codeに対してresponse_forを返します。excのmessageは、レスポンスに入れません。

例外を捕まえる場所を散らさず、アプリの共通の境界に置きます。

保存したら、bash /opt/lab/checks/fa-problem-lab/07-contract.shで確認してください。

予想していないエラーも隠す

/root/work/fa-problem-lab/service.pyで、create_app()は、ハンドラーをインストールして、GET /fail/{code}でDomainError(code, 内部の文言)を出します。ただしcode=boomならRuntimeErrorを出します。RuntimeErrorのハンドラーは、code=internalの固定の500の問題レスポンスを返します。

テストで例外の再送出を無効にして、実際の500のレスポンスのバイト列を確認します。

保存したら、bash /opt/lab/checks/fa-problem-lab/08-contract.shで確認してください。