Skip to content

tech-debt(validation) — decoded body の mixed を absent/present を表現する型に置き換える #248

@wadakatu

Description

@wadakatu

背景

PR #247(issue #246 の修正)で、JSON リテラルの null ボディを「ボディ無し」と区別するため内部マーカー enum PresentJsonNull を導入した。type-design レビューで、根本的には「decoded body」が array | scalar | null | PresentJsonNull という名前の無い union を mixed で表現している点が設計上の弱点であると指摘された(マーカー enum 自体はその場しのぎとして妥当だが end-state ではない)。

現状

body validator の入口は body を mixed で受け取る:

  • OpenApiResponseValidator::validate()$responseBody
  • OpenApiRequestValidator::validate()$requestBody
  • ResponseBodyValidator::validate() / RequestBodyValidator::validate()

PresentJsonNull マーカーは全 consumer が手動で unwrap する必要があり、unwrap の idiom も2種類ある:

  • orchestrator: $responseBody instanceof PresentJsonNull ? null : $responseBodyOpenApiResponseValidator
  • body validator: $bodyWasPresent フラグへの2文展開(ResponseBodyValidator / RequestBodyValidator

問題

  • mixed のため「マーカーは必ず unwrap される」契約を PHPStan が強制できない。新しい consumer が unwrap を忘れると、マーカー object が下流(ObjectConverter / opis/json-schema / strict-required walker)へ流れ、型エラーまたは silent な誤判定になる。
  • unwrap 後、「ボディが present だった」事実がマーカーから別変数($bodyWasPresent)へ移り、マーカーが排除したはずの曖昧状態が1スコープ深く再現されている。

やること

  • final readonly class DecodedBody { public bool $present; public mixed $value; } のような envelope 型を導入し、named constructor absent() / present(mixed $value) を提供する。
  • アダプタが envelope を生成し、validator が envelope を受け取る。mixed body パラメータと PresentJsonNull マーカー / $bodyWasPresent フラグを廃する。
  • 注意: OpenApiResponseValidator::validate() / OpenApiRequestValidator::validate() の公開シグネチャ変更になるため、SemVer 上は v2 メジャーバンプ案件(tech-debt(validator): make OpenApiResponseValidator strictRequiredTracker ctor param required (v2) #234 と同類)。あるいは BC を維持する段階的移行を設計する。

ゴール

  • decoded body の absent / present 区別が単一の値で end-to-end に保持される。
  • consumer の unwrap 漏れを PHPStan が静的に検出できる。
  • PresentJsonNull マーカーと $bodyWasPresent フラグが不要になる。

関連

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