feat(schema): enforce discriminator.mapping via if/then lowering#263
Merged
Conversation
Replace the strip-and-E_USER_WARNING handling of `discriminator.mapping` with real enforcement: the converter now lowers `discriminator` + `mapping` into a Draft-07 `allOf` of an unknown-value guard plus one `if`/`then` per mapping value, so the discriminator value steers validation toward a single branch. A body that lies about its type (e.g. `kty: RSA` carrying EC-only fields) now fails instead of passing the underlying oneOf/anyOf union. The mapping pointers are resolved against the resolved root spec, which is threaded from the request/response orchestrators through the body validators into the converter via a new DiscriminatorContext. A signature-based recursion guard handles the base<->subtype cycle created by eager $ref inlining (the inheritance idiom) and terminates without combinatorial blow-up. Malformed discriminator blocks throw MalformedDiscriminatorException, surfaced as a clean validation failure by the existing body-validator boundary. Enforcement is gated by `enforce_discriminator` (default on), wired through the Laravel config/trait and the PHPUnit OpenApiCoverageExtension. The removed warning also fixes Laravel consumers whose error handler turned it into a fatal ErrorException. Closes #262
…nator Update supported-features.md (discriminator now enforced via if/then lowering, opt-out flag, malformed/self-reference/nullable notes; remove it from the stripped list and the warning-channel table), setup.md (new enforce_discriminator parameter + Laravel config key), and UPGRADING.md (new v1.7.0 -> v1.8.0 behavioural-change section).
The recursion guard's signature was computed from the discriminator propertyName plus the mapping KEY set only, excluding the target pointers. Two genuinely distinct discriminators that share a property name and key set (but map to different schemas) collided, so a nested distinct discriminator re-encountered inside a `then` branch was stripped instead of lowered — a silent under-enforcement. Fold the resolved target into each signature pair so only an identical mapping (the self-reference case the guard is for) collides. Adds a regression test plus enforce=false-with-root and nullable+discriminator coverage.
…orcement Adds the previously-missing coverage for the enforce_discriminator gate: the PHPUnit extension parameter (off disables, absent keeps default-on), the Laravel trait wiring (default-on, explicit-off, string coercion, loud failure on a non-boolean value), and request-side enforcement against the JWKS implicit-inheritance fixture (a POST endpoint where the bare union would pass but the lowered discriminator catches a lying body).
The documented PHPUnit opt-out used value="off", but the boolean parameter reader only treats false/0/no as off, so "off" left enforcement on. Switch the docs/comment to value="false" (the repo convention for boolean params). Also correct the self-reference limitation note: the inner re-appearance is stripped without re-lowering, not "degraded to the unknown-value guard only".
e2c292a to
6ae42c2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
discriminator.mappingを「strip + 一度きりのE_USER_WARNING」から、Draft-07 のif/thenlowering による実際の enforcement へ置き換えます。型を偽った polymorphic body(例:kty: RSAなのに EC 専用フィールドを持つ)が、従来は oneOf/anyOf union のいずれかに合致すれば通っていたのを、正しく FAIL させます。変更内容
discriminatorは OAS 固有キーで opis(Draft 07)が解釈できないため従来は strip していました。本 PR ではdiscriminator+mappingをallOf(unknown-value ガード + 値ごとのif/then、thenは解決済み subtype スキーマ)へ lowering し、discriminator 値で単一ブランチへ検証を導きます。OpenApiSchemaConverterからdiscriminatorの strip と警告メソッドを撤去し、lowerDiscriminator()を追加。mapping ポインタ(#/...または bare-name shorthand)を解決し、各 subtype を再帰 convertconvert()へ伝播するDiscriminatorContextを新設(mapping ポインタの解決に必要)MalformedDiscriminatorException(RuntimeException派生)を throw し、既存の body-validator 境界で loud な validation failure として surfaceenforce_discriminator(既定 ON)で gate。Laravel config/trait と PHPUnitOpenApiCoverageExtensionに配線。offで従来どおり strip(警告なし)HandleExceptionsがE_USER_WARNINGを致命的なErrorExceptionに変換していた問題(contract テストが落ちる)も解消tests/fixtures/specs/jwks.json(暗黙継承 = motivating case)+ 統合テスト(valid RSA/EC・lying type FAIL・unknown kty FAIL)。composer ciグリーン(1871 tests / PHPStan / cs-fixer)supported-features.md/setup.md/UPGRADING.md(v1.7.0 → v1.8.0 の挙動変化節)を更新挙動変化(要確認)
discriminator.mappingのE_USER_WARNINGを撤去既知の制約(docs 記載)
nullable+discriminatorはnullbody が discriminated-object ブランチで FAIL関連情報