Report all errors when asserting with "Each" rule

The intention of the "assert()" method is to show all the errors that a
given input may have. The implementation of the "assert()" method in the
"Each" rule, on the other hand, only reports the first error of each
element of the input.

This commit makes "Each" show all the validation failures of each
element of the input. Also, the implementation of
"AbstractRule::check()" is simply a proxy for the "assert()" method,
and since the "Each" rule extends that class, this commit creates a
custom implementation of the "check()" method.

Co-authored-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
Dylan T 2020-05-16 11:06:19 +01:00 committed by Henrique Moody
parent 7154e90522
commit 3501192293
No known key found for this signature in database
GPG key ID: 221E9281655813A6
3 changed files with 126 additions and 22 deletions

View file

@ -55,7 +55,7 @@ final class Each extends AbstractRule
$exceptions = [];
foreach ($input as $value) {
try {
$this->rule->check($value);
$this->rule->assert($value);
} catch (ValidationException $exception) {
$exceptions[] = $exception;
}
@ -70,13 +70,27 @@ final class Each extends AbstractRule
}
}
/**
* {@inheritDoc}
*/
public function check($input): void
{
if (!$this->isIterable($input)) {
throw $this->reportError($input);
}
foreach ($input as $value) {
$this->rule->check($value);
}
}
/**
* {@inheritDoc}
*/
public function validate($input): bool
{
try {
$this->assert($input);
$this->check($input);
} catch (ValidationException $exception) {
return false;
}

View file

@ -0,0 +1,76 @@
--CREDITS--
Henrique Moody <henriquemoody@gmail.com>
--DESCRIPTION--
The previous output was:
default must be of type string
- default must be of type string
--FILE--
<?php
declare(strict_types=1);
use Respect\Validation\Exceptions\NestedValidationException;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Rules\ArrayType;
use Respect\Validation\Rules\BoolType;
use Respect\Validation\Rules\Each;
use Respect\Validation\Rules\Key;
use Respect\Validation\Rules\OneOf;
use Respect\Validation\Rules\StringType;
use Respect\Validation\Rules\StringVal;
use Respect\Validation\Validator;
require 'vendor/autoload.php';
$validator = new Validator(
new Each(
new Validator(
new Key(
'default',
new OneOf(
new StringType(),
new BoolType()
),
false
),
new Key(
'description',
new StringVal(),
false
),
new Key(
'children',
new ArrayType(),
false
)
)
)
);
$input = [
[
'default' => 2,
'description' => [],
'children' => ['nope'],
],
];
try {
$validator->check($input);
} catch (ValidationException $exception) {
echo $exception->getMessage().PHP_EOL;
}
try {
$validator->assert($input);
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage();
}
?>
--EXPECT--
default must be of type string
- These rules must pass for `{ "default": 2, "description": { }, "children": { "nope" } }`
- Only one of these rules must pass for default
- default must be of type string
- default must be of type boolean
- description must be a string

View file

@ -17,6 +17,8 @@ use Respect\Validation\Test\RuleTestCase;
use Respect\Validation\Validatable;
use SplStack;
use stdClass;
use Traversable;
use function range;
/**
* @group rule
@ -40,6 +42,8 @@ final class EachTest extends RuleTestCase
return [
[$rule, []],
[$rule, [1, 2, 3, 4, 5]],
[$rule, $this->createTraversableInput(1, 5)],
[$rule, $this->createStdClassInput(1, 5)],
];
}
@ -57,43 +61,39 @@ final class EachTest extends RuleTestCase
[$rule, false],
[$rule, ['', 2, 3, 4, 5]],
[$rule, ['a', 2, 3, 4, 5]],
[$rule, $this->createTraversableInput(1, 5)],
[$rule, $this->createStdClassInput(1, 5)],
];
}
/**
* @test
*/
public function itShouldValidateTraversable(): void
public function itShouldAssertEachValue(): void
{
$validatable = $this->createMock(Validatable::class);
$validatable
->expects(self::at(0))
->method('check')
->with('A');
->method('assert')
->with(1);
$validatable
->expects(self::at(1))
->method('check')
->with('B');
->method('assert')
->with(2);
$validatable
->expects(self::at(2))
->method('check')
->with('C');
->method('assert')
->with(3);
$rule = new Each($validatable);
$input = new SplStack();
$input->push('C');
$input->push('B');
$input->push('A');
self::assertValidInput($rule, $input);
$rule->assert(range(1, 3));
}
/**
* @test
*/
public function itShouldValidateStdClass(): void
public function itShouldCheckEachValue(): void
{
$validatable = $this->createMock(Validatable::class);
@ -111,12 +111,26 @@ final class EachTest extends RuleTestCase
->with(3);
$rule = new Each($validatable);
$rule->check(range(1, 3));
}
$input = new stdClass();
$input->foo = 1;
$input->bar = 2;
$input->baz = 3;
private function createTraversableInput(int $firstValue, int $lastValue): Traversable
{
$input = new SplStack();
foreach (range($firstValue, $lastValue) as $value) {
$input->push($value);
}
self::assertValidInput($rule, $input);
return $input;
}
private function createStdClassInput(int $firstValue, int $lastValue): stdClass
{
$input = [];
foreach (range($firstValue, $lastValue) as $value) {
$input[] = $value;
}
return (object) $input;
}
}