背景
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 : $responseBody(OpenApiResponseValidator)
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 フラグが不要になる。
関連
背景
PR #247(issue #246 の修正)で、JSON リテラルの
nullボディを「ボディ無し」と区別するため内部マーカー enumPresentJsonNullを導入した。type-design レビューで、根本的には「decoded body」がarray | scalar | null | PresentJsonNullという名前の無い union をmixedで表現している点が設計上の弱点であると指摘された(マーカー enum 自体はその場しのぎとして妥当だが end-state ではない)。現状
body validator の入口は body を
mixedで受け取る:OpenApiResponseValidator::validate()の$responseBodyOpenApiRequestValidator::validate()の$requestBodyResponseBodyValidator::validate()/RequestBodyValidator::validate()PresentJsonNullマーカーは全 consumer が手動で unwrap する必要があり、unwrap の idiom も2種類ある:$responseBody instanceof PresentJsonNull ? null : $responseBody(OpenApiResponseValidator)$bodyWasPresentフラグへの2文展開(ResponseBodyValidator/RequestBodyValidator)問題
mixedのため「マーカーは必ず unwrap される」契約を PHPStan が強制できない。新しい consumer が unwrap を忘れると、マーカー object が下流(ObjectConverter/opis/json-schema/ strict-required walker)へ流れ、型エラーまたは silent な誤判定になる。$bodyWasPresent)へ移り、マーカーが排除したはずの曖昧状態が1スコープ深く再現されている。やること
final readonly class DecodedBody { public bool $present; public mixed $value; }のような envelope 型を導入し、named constructorabsent()/present(mixed $value)を提供する。mixedbody パラメータとPresentJsonNullマーカー /$bodyWasPresentフラグを廃する。OpenApiResponseValidator::validate()/OpenApiRequestValidator::validate()の公開シグネチャ変更になるため、SemVer 上は v2 メジャーバンプ案件(tech-debt(validator): make OpenApiResponseValidator strictRequiredTracker ctor param required (v2) #234 と同類)。あるいは BC を維持する段階的移行を設計する。ゴール
PresentJsonNullマーカーと$bodyWasPresentフラグが不要になる。関連