Repository navigation
fix: apply required and additionalProperties to an empty object - #8
Merged
Merged
Conversation
SchemaValidator accepted `{}` against every published schema, however many
fields that schema declared required. Confirmed against inventory.v1,
system.v2, updates.v2, logins.v1 and backup.v3: all five returned no errors.
json_decode('{}', true) returns [], and array_is_list([]) is true, so check()
dispatched an empty object to checkArray() - which returns immediately when the
schema declares no `items`. checkObject() holds both `required` and
`additionalProperties`, and was never reached. matchesType() already carried
this exact exception one screen down, where 'object' accepts $value === [], so
the type gate passed first and the payload sailed through everything after it.
Dispatch now consults the schema before PHP's shape: a node the schema declares
as type `object` reaches checkObject() even when it arrives empty. A node with
no declared type keeps the old behaviour, and an empty array against type
`array` is still a list - so nothing that validated before stops validating,
which the new test asserts directly.
The effect downstream was not a lenient accept but a worse error. A payload the
validator was supposed to refuse by name instead reached code that indexed a
key which was not there: an inventory report of `{}` returned a 500 with a
correlation id rather than a 422 naming the missing field, and a manifest whose
encryption block had been emptied passed both the declaration schema and the
manifest signature check before dying on an undefined key.
It survived 368 tests because the suite empties an object by unsetting one key
at a time, so every test payload kept at least one key and the fully-empty case
was never constructed. tests/SchemaValidatorTest.php now covers the validator's
own behaviour rather than any one schema's rules.
No schema file was edited and no fixture was regenerated. The rules were
already published; they were not being applied.
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.
SchemaValidatoraccepted{}against every published schema, however many fields that schema declaredrequired. Confirmed againstinventory.v1,system.v2,updates.v2,logins.v1andbackup.v3— all five returned no errors at all, while a single unrecognised field was correctly rejected by all five.Why
json_decode('{}', true)returns[], andarray_is_list([])istrue, socheck()dispatched an empty object tocheckArray()— which returns immediately when the schema declares noitems.checkObject()holds bothrequiredandadditionalProperties, and was never reached.matchesType()already carried this exact exception one screen down ('object'accepts$value === []), so the type gate passed first.Downstream the effect was not a lenient accept but a worse error: a payload the validator was supposed to refuse by name instead reached code that indexed a key which was not there. An inventory report of
{}returned a 500 with a correlation id rather than a 422 naming the missing field. A manifest whoseencryptionblock had been emptied passed both the declaration schema and the manifest signature check before dying on an undefined key.What changed
Dispatch consults the schema before PHP's shape. A node the schema declares as
type: objectreachescheckObject()even when it arrives empty. A node with no declaredtypekeeps the old behaviour, and an empty array againsttype: arrayis still a list — so nothing that validated before stops validating.tests/SchemaValidatorTest.phpis new and covers the validator's own behaviour rather than any one schema's rules: every published schema refuses{}, a nested empty object is refused, an empty list is still a list, and every valid fixture still validates unchanged.It survived 368 tests because the suite empties an object by unsetting one key at a time, so every test payload kept at least one key and the fully-empty case was never constructed.
Not done
No schema file was edited and no fixture was regenerated — the rules were already published, they were simply not being applied. Nothing was relaxed for existing callers: a connector sending
{}was already broken and now learns so with a 422 that names the fields.Checks
composer test— 384 passed (2,797 assertions); was 368 before the new filecomposer phpstan— no errorscomposer.jsonandProtocol::VERSION, asVersionTestrequiresThis is first in the release order, so nothing downstream can consume the fix until it is tagged and Packagist serves it.