mirror of
https://github.com/Respect/Validation.git
synced 2024-06-08 08:42:15 +02:00
Update the validation engine of the "Not" rule
With the new validation engine, the Not rule becomes ridiculously
uncomplicated.
I didn't see the need to keep the "NonNegatable." Some rules' messages
can indeed be confusing[1], but we have way more granularity control
now.
[1]: fc8230acef
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
parent
99dc8720ce
commit
1fd60edcb1
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation;
|
||||
|
||||
interface NonNegatable
|
||||
{
|
||||
}
|
|
@ -9,132 +9,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Attributes\ExceptionClass;
|
||||
use Respect\Validation\Exceptions\ComponentException;
|
||||
use Respect\Validation\Exceptions\NestedValidationException;
|
||||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\NonNegatable;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function current;
|
||||
use function sprintf;
|
||||
|
||||
#[ExceptionClass(NestedValidationException::class)]
|
||||
#[Template(
|
||||
'All of the required rules must pass for {{name}}',
|
||||
'None of there rules must pass for {{name}}',
|
||||
Not::TEMPLATE_NONE,
|
||||
)]
|
||||
#[Template(
|
||||
'These rules must pass for {{name}}',
|
||||
'These rules must not pass for {{name}}',
|
||||
Not::TEMPLATE_SOME,
|
||||
)]
|
||||
final class Not extends AbstractRule
|
||||
final class Not extends Wrapper
|
||||
{
|
||||
public const TEMPLATE_NONE = '__none__';
|
||||
public const TEMPLATE_SOME = '__some__';
|
||||
|
||||
private readonly Validatable $rule;
|
||||
|
||||
public function __construct(Validatable $rule)
|
||||
{
|
||||
$this->rule = $this->extractNegatedRule($rule);
|
||||
}
|
||||
|
||||
public function getNegatedRule(): Validatable
|
||||
{
|
||||
return $this->rule;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->rule->setName($name);
|
||||
|
||||
return parent::setName($name);
|
||||
}
|
||||
|
||||
public function setTemplate(string $template): static
|
||||
{
|
||||
$this->rule->setTemplate($template);
|
||||
|
||||
return parent::setTemplate($template);
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
return $this->rule->validate($input) === false;
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
{
|
||||
if ($this->validate($input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rule = $this->rule;
|
||||
if ($rule instanceof AllOf) {
|
||||
$rule = $this->absorbAllOf($rule, $input);
|
||||
}
|
||||
|
||||
$exception = $rule->reportError($input);
|
||||
$exception->updateMode(ValidationException::MODE_NEGATIVE);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
return $this->rule->evaluate($input)->withInvertedMode();
|
||||
}
|
||||
|
||||
private function absorbAllOf(AllOf $rule, mixed $input): Validatable
|
||||
{
|
||||
$rules = $rule->getRules();
|
||||
while (($current = array_shift($rules))) {
|
||||
$rule = $current;
|
||||
if (!$rule instanceof AllOf) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$rule->validate($input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules = $rule->getRules();
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
private function extractNegatedRule(Validatable $rule): Validatable
|
||||
{
|
||||
if ($rule instanceof NonNegatable) {
|
||||
throw new ComponentException(
|
||||
sprintf(
|
||||
'"%s" can not be wrapped in Not()',
|
||||
$rule::class
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($rule instanceof self && $rule->getNegatedRule() instanceof self) {
|
||||
return $this->extractNegatedRule($rule->getNegatedRule()->getNegatedRule());
|
||||
}
|
||||
|
||||
if (!$rule instanceof AllOf) {
|
||||
return $rule;
|
||||
}
|
||||
|
||||
$rules = $rule->getRules();
|
||||
if (count($rules) === 1) {
|
||||
return $this->extractNegatedRule(current($rules));
|
||||
}
|
||||
|
||||
return $rule;
|
||||
return parent::evaluate($input)->withInvertedMode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,87 +10,39 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Rules;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
use Respect\Validation\Validatable;
|
||||
use Respect\Validation\Validator;
|
||||
use Respect\Validation\Test\Rules\Stub;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
#[Group('rule')]
|
||||
#[CoversClass(Not::class)]
|
||||
final class NotTest extends TestCase
|
||||
final class NotTest extends RuleTestCase
|
||||
{
|
||||
#[Test]
|
||||
#[DataProvider('providerForValidNot')]
|
||||
public function not(Validatable $rule, mixed $input): void
|
||||
public function shouldInvertTheResultOfWrappedRule(): void
|
||||
{
|
||||
$not = new Not($rule);
|
||||
$wrapped = Stub::fail(2);
|
||||
|
||||
self::assertTrue($not->evaluate($input)->isValid);
|
||||
$rule = new Not($wrapped);
|
||||
|
||||
self::assertEquals(
|
||||
$rule->evaluate('input'),
|
||||
$wrapped->evaluate('input')->withInvertedMode()
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('providerForInvalidNot')]
|
||||
public function notNotHaha(Validatable $rule, mixed $input): void
|
||||
/** @return iterable<string, array{Not, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
$not = new Not($rule);
|
||||
|
||||
self::assertFalse($not->evaluate($input)->isValid);
|
||||
yield 'invert fail' => [new Not(Stub::fail(1)), []];
|
||||
yield 'invert success x2' => [new Not(new Not(Stub::pass(1))), []];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('providerForSetName')]
|
||||
public function notSetName(Validatable $rule): void
|
||||
/** @return iterable<string, array{Not, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
{
|
||||
$not = new Not($rule);
|
||||
$not->setName('Foo');
|
||||
|
||||
self::assertEquals('Foo', $not->getName());
|
||||
self::assertEquals('Foo', $not->getNegatedRule()->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{Validatable, mixed}>
|
||||
*/
|
||||
public static function providerForValidNot(): array
|
||||
{
|
||||
return [
|
||||
[new IntVal(), ''],
|
||||
[new IntVal(), 'aaa'],
|
||||
[new AllOf(new NoWhitespace(), new Digit()), 'as df'],
|
||||
[new AllOf(new NoWhitespace(), new Digit()), '12 34'],
|
||||
[new AllOf(new AllOf(new NoWhitespace(), new Digit())), '12 34'],
|
||||
[new AllOf(new NoneOf(new NumericVal(), new IntVal())), 13.37],
|
||||
[new NoneOf(new NumericVal(), new IntVal()), 13.37],
|
||||
[Validator::noneOf(Validator::numericVal(), Validator::intVal()), 13.37],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{Validatable, mixed}>
|
||||
*/
|
||||
public static function providerForInvalidNot(): array
|
||||
{
|
||||
return [
|
||||
[new IntVal(), 123],
|
||||
[new AllOf(new AnyOf(new NumericVal(), new IntVal())), 13.37],
|
||||
[new AnyOf(new NumericVal(), new IntVal()), 13.37],
|
||||
[Validator::anyOf(Validator::numericVal(), Validator::intVal()), 13.37],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{Validatable}>
|
||||
*/
|
||||
public static function providerForSetName(): array
|
||||
{
|
||||
return [
|
||||
'non-allOf' => [new IntVal()],
|
||||
'allOf' => [new AllOf(new NumericVal(), new IntVal())],
|
||||
'not' => [new Not(new Not(new IntVal()))],
|
||||
'allOf with name' => [Validator::intVal()->setName('Bar')],
|
||||
'noneOf' => [Validator::noneOf(Validator::numericVal(), Validator::intVal())],
|
||||
];
|
||||
yield 'invert pass' => [new Not(Stub::pass(1)), []];
|
||||
yield 'invert fail x2' => [new Not(new Not(Stub::fail(1))), []];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue