mirror of
https://github.com/Respect/Validation.git
synced 2024-04-30 05:32:50 +02:00
Make KeySet impossible to wrap in not(), fix structure message
The use case for negating a keyset is very confusing, and can lead to validators that don't do what they expect. This commit introduces NonNegatable rules, which will throw a Component exception if you try to wrap them in `Not`. This change was necessary to ensure proper message reporting when extra keys exist on the keyset. This fixes #1349
This commit is contained in:
parent
493a665e99
commit
fc8230acef
|
@ -43,6 +43,8 @@ v::keySet(
|
|||
)->validate($dict); // true
|
||||
```
|
||||
|
||||
It is not possible to negate `keySet()` rules with `not()`.
|
||||
|
||||
The keys' order is not considered in the validation.
|
||||
|
||||
## Categorization
|
||||
|
@ -55,6 +57,7 @@ The keys' order is not considered in the validation.
|
|||
|
||||
Version | Description
|
||||
--------|-------------
|
||||
2.3.0 | KeySet is NonNegatable, fixed message with extra keys
|
||||
1.0.0 | Created
|
||||
|
||||
***
|
||||
|
|
|
@ -17,6 +17,7 @@ use function count;
|
|||
final class KeySetException extends GroupedValidationException implements NonOmissibleException
|
||||
{
|
||||
public const STRUCTURE = 'structure';
|
||||
public const STRUCTURE_EXTRA = 'structure_extra';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
|
@ -26,11 +27,7 @@ final class KeySetException extends GroupedValidationException implements NonOmi
|
|||
self::NONE => 'All of the required rules must pass for {{name}}',
|
||||
self::SOME => 'These rules must pass for {{name}}',
|
||||
self::STRUCTURE => 'Must have keys {{keys}}',
|
||||
],
|
||||
self::MODE_NEGATIVE => [
|
||||
self::NONE => 'None of these rules must pass for {{name}}',
|
||||
self::SOME => 'These rules must not pass for {{name}}',
|
||||
self::STRUCTURE => 'Must not have keys {{keys}}',
|
||||
self::STRUCTURE_EXTRA => 'Must not have keys {{extraKeys}}',
|
||||
],
|
||||
];
|
||||
|
||||
|
@ -39,6 +36,10 @@ final class KeySetException extends GroupedValidationException implements NonOmi
|
|||
*/
|
||||
protected function chooseTemplate(): string
|
||||
{
|
||||
if (count($this->getParam('extraKeys'))) {
|
||||
return self::STRUCTURE_EXTRA;
|
||||
}
|
||||
|
||||
if (count($this->getChildren()) === 0) {
|
||||
return self::STRUCTURE;
|
||||
}
|
||||
|
|
18
library/NonNegatable.php
Normal file
18
library/NonNegatable.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation;
|
||||
|
||||
/** Interface for validation rules */
|
||||
/**
|
||||
* @author Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
*/
|
||||
interface NonNegatable
|
||||
{
|
||||
}
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Exceptions\ComponentException;
|
||||
use Respect\Validation\NonNegatable;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
use function array_key_exists;
|
||||
|
@ -24,13 +25,18 @@ use function is_array;
|
|||
* @author Emmerson Siqueira <emmersonsiqueira@gmail.com>
|
||||
* @author Henrique Moody <henriquemoody@gmail.com>
|
||||
*/
|
||||
final class KeySet extends AbstractWrapper
|
||||
final class KeySet extends AbstractWrapper implements NonNegatable
|
||||
{
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $keys;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $extraKeys = [];
|
||||
|
||||
/**
|
||||
* @var Key[]
|
||||
*/
|
||||
|
@ -127,6 +133,10 @@ final class KeySet extends AbstractWrapper
|
|||
unset($input[$keyRule->getReference()]);
|
||||
}
|
||||
|
||||
foreach ($input as $extraKey => &$ignoreValue) {
|
||||
$this->extraKeys[] = $extraKey;
|
||||
}
|
||||
|
||||
return count($input) == 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,16 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Exceptions\ComponentException;
|
||||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Respect\Validation\NonNegatable;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function current;
|
||||
use function get_class;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* @author Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
|
@ -97,6 +101,15 @@ final class Not extends AbstractRule
|
|||
|
||||
private function extractNegatedRule(Validatable $rule): Validatable
|
||||
{
|
||||
if ($rule instanceof NonNegatable) {
|
||||
throw new ComponentException(
|
||||
sprintf(
|
||||
'"%s" can not be wrapped in Not()',
|
||||
get_class($rule)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($rule instanceof self && $rule->getNegatedRule() instanceof self) {
|
||||
return $this->extractNegatedRule($rule->getNegatedRule()->getNegatedRule());
|
||||
}
|
||||
|
|
|
@ -171,6 +171,36 @@ final class KeySetTest extends TestCase
|
|||
$keySet->assert($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function shouldWarnOfExtraKeysWithMessage(): void
|
||||
{
|
||||
$input = ['foo' => 123, 'bar' => 456];
|
||||
|
||||
$key1 = new Key('foo', new AlwaysValid(), true);
|
||||
|
||||
$keySet = new KeySet($key1);
|
||||
|
||||
$this->expectException(KeySetException::class);
|
||||
$this->expectExceptionMessage('Must not have keys `{ "bar" }`');
|
||||
|
||||
$keySet->assert($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cannotBeNegated(): void
|
||||
{
|
||||
$key1 = new Key('foo', new AlwaysValid(), true);
|
||||
|
||||
$this->expectException(ComponentException::class);
|
||||
$this->expectExceptionMessage('"Respect\Validation\Rules\KeySet" can not be wrapped in Not()');
|
||||
|
||||
new Not(new KeySet($key1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue