Skip to content

tech-debt(adapters) — 非空・非JSON Content-Type のボディが silent に「ボディ無し」扱いされる #251

@wadakatu

Description

@wadakatu

背景

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.phpextractRequestBody() / extractJsonBody()
  • src/Symfony/OpenApiAssertions.phpextractSymfonyJsonBody()
if ($contentType !== '' && !str_contains(strtolower($contentType), 'json')) {
    return DecodedBody::absent();
}

その結果、text/plainapplication/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 に落ちる、または明示的な診断が出る

関連

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions