背景
PR #250(issue #248 — decoded body の DecodedBody envelope 化)の multi-agent レビュー(/pr-review-toolkit:review-pr)で silent-failure-hunter が指摘。PR #248 以前から存在する既存ソフトスポットであり、envelope 化とは独立のため #250 のスコープ外として切り出し。
現状
リクエスト/レスポンスの 非空ボディ が 非 JSON Content-Type で届いたとき、3 つのアダプタ抽出メソッドはボディを破棄して「ボディ無し(absent)」を返します。
src/Laravel/ValidatesOpenApiSchema.php — extractRequestBody() / extractJsonBody()
src/Symfony/OpenApiAssertions.php — extractSymfonyJsonBody()
if ($contentType !== '' && !str_contains(strtolower($contentType), 'json')) {
return DecodedBody::absent();
}
その結果、text/plain や application/x-www-form-urlencoded 等の 非空ボディが付いたリクエストは、下流の RequestBodyValidator から見ると「ボディ無し」と区別できません。
問題
spec が optional な JSON request body を宣言しているエンドポイントに、実際には非 JSON の非空ボディを送ったリクエストが、エラーも診断も出さず通過します(required: true のときだけ "Request body is empty" で落ちる)。
「ボディが全く無い」状態と「非空だが非 JSON でパースできないボディがあった」状態は contract 上意味が異なります。後者は contract 関連情報であるにもかかわらず、アダプタ境界で破棄されています。contract testing ツールの「不正は loud に落とす」原則(issue #246 と同種の議論)に反します。
なお DecodedBody envelope(#248 で導入)は、この第 3 の状態を表現する適切な器になり得ます。
やること
- 「ボディ無し」と「非空だが非 JSON Content-Type のボディ」を区別する。後者を
DecodedBody で表現できる形に拡張するか、別の経路で診断を出す
- 副次的に、Content-Type 判定の
str_contains(strtolower($contentType), 'json') は緩く(例: application/jsonsomethingweird も JSON 扱い)、ResponseBodyValidator が既に使う ContentTypeMatcher::isJsonContentType() に揃えると一貫する
- TDD: 非 JSON の非空 request body が optional な JSON body エンドポイントに対して silent pass しないことを検証する失敗テストを先に追加
ゴール
- 非空・非 JSON ボディが「ボディ無し」と混同されず、contract 上の判断材料が失われない
- 該当の silent-pass ケースが loud に落ちる、または明示的な診断が出る
関連
背景
PR #250(issue #248 — decoded body の
DecodedBodyenvelope 化)の multi-agent レビュー(/pr-review-toolkit:review-pr)で silent-failure-hunter が指摘。PR #248 以前から存在する既存ソフトスポットであり、envelope 化とは独立のため #250 のスコープ外として切り出し。現状
リクエスト/レスポンスの 非空ボディ が 非 JSON Content-Type で届いたとき、3 つのアダプタ抽出メソッドはボディを破棄して「ボディ無し(absent)」を返します。
src/Laravel/ValidatesOpenApiSchema.php—extractRequestBody()/extractJsonBody()src/Symfony/OpenApiAssertions.php—extractSymfonyJsonBody()その結果、
text/plainやapplication/x-www-form-urlencoded等の 非空ボディが付いたリクエストは、下流のRequestBodyValidatorから見ると「ボディ無し」と区別できません。問題
spec が optional な JSON request body を宣言しているエンドポイントに、実際には非 JSON の非空ボディを送ったリクエストが、エラーも診断も出さず通過します(
required: trueのときだけ "Request body is empty" で落ちる)。「ボディが全く無い」状態と「非空だが非 JSON でパースできないボディがあった」状態は contract 上意味が異なります。後者は contract 関連情報であるにもかかわらず、アダプタ境界で破棄されています。contract testing ツールの「不正は loud に落とす」原則(issue #246 と同種の議論)に反します。
なお
DecodedBodyenvelope(#248 で導入)は、この第 3 の状態を表現する適切な器になり得ます。やること
DecodedBodyで表現できる形に拡張するか、別の経路で診断を出すstr_contains(strtolower($contentType), 'json')は緩く(例:application/jsonsomethingweirdも JSON 扱い)、ResponseBodyValidatorが既に使うContentTypeMatcher::isJsonContentType()に揃えると一貫するゴール
関連
null/ scalar is silently treated as "no body" #246(literal JSON null / スカラーボディの silent「ボディ無し」扱い tech-debt — 同種の問題)