mirror of
https://github.com/Respect/Validation.git
synced 2024-06-08 00:32:16 +02:00
Update the validation engine of wrapper-based rules
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
parent
e341fef5c0
commit
99dc8720ce
61
library/Helpers/CanExtractRules.php
Normal file
61
library/Helpers/CanExtractRules.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Helpers;
|
||||
|
||||
use Respect\Validation\Exceptions\ComponentException;
|
||||
use Respect\Validation\Validatable;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function current;
|
||||
use function sprintf;
|
||||
|
||||
trait CanExtractRules
|
||||
{
|
||||
private function extractSingle(Validatable $rule, string $class): Validatable
|
||||
{
|
||||
if ($rule instanceof Validator) {
|
||||
return $this->extractSingleFromValidator($rule, $class);
|
||||
}
|
||||
|
||||
if (!$rule instanceof $class) {
|
||||
throw new ComponentException(sprintf(
|
||||
'Could not extract rule %s from %s',
|
||||
$class,
|
||||
$rule::class,
|
||||
));
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Validatable> $rules
|
||||
*
|
||||
* @return array<Validatable>
|
||||
*/
|
||||
private function extractMany(array $rules, string $class): array
|
||||
{
|
||||
return array_map(fn (Validatable $rule) => $this->extractSingle($rule, $class), $rules);
|
||||
}
|
||||
|
||||
private function extractSingleFromValidator(Validator $rule, string $class): Validatable
|
||||
{
|
||||
$rules = $rule->getRules();
|
||||
if (count($rules) !== 1) {
|
||||
throw new ComponentException(sprintf(
|
||||
'Validator must contain exactly one rule'
|
||||
));
|
||||
}
|
||||
|
||||
return $this->extractSingle(current($rules), $class);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
abstract class AbstractWrapper extends AbstractRule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Validatable $validatable
|
||||
) {
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
return $this->validatable->evaluate($input);
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
{
|
||||
$this->validatable->assert($input);
|
||||
}
|
||||
|
||||
public function check(mixed $input): void
|
||||
{
|
||||
$this->validatable->check($input);
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
return $this->validatable->validate($input);
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->validatable->setName($name);
|
||||
|
||||
return parent::setName($name);
|
||||
}
|
||||
|
||||
public function setTemplate(string $template): static
|
||||
{
|
||||
$this->validatable->setTemplate($template);
|
||||
|
||||
return parent::setTemplate($template);
|
||||
}
|
||||
}
|
|
@ -9,153 +9,70 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Attributes\ExceptionClass;
|
||||
use Respect\Validation\Exceptions\ComponentException;
|
||||
use Respect\Validation\Exceptions\NonOmissibleValidationException;
|
||||
use Respect\Validation\Helpers\CanBindEvaluateRule;
|
||||
use Respect\Validation\Helpers\CanExtractRules;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\NonNegatable;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
use function array_key_exists;
|
||||
use function array_diff;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function current;
|
||||
use function is_array;
|
||||
|
||||
#[ExceptionClass(NonOmissibleValidationException::class)]
|
||||
#[Template(
|
||||
'All of the required rules must pass for {{name}}',
|
||||
'',
|
||||
self::TEMPLATE_NONE,
|
||||
'Must have keys {{missingKeys}} in {{name}}',
|
||||
'Must not have keys {{missingKeys}} in {{name}}',
|
||||
self::TEMPLATE_MISSING,
|
||||
)]
|
||||
#[Template(
|
||||
'These rules must pass for {{name}}',
|
||||
'',
|
||||
self::TEMPLATE_SOME,
|
||||
'Must not have keys {{extraKeys}} in {{name}}',
|
||||
'Must have keys {{extraKeys}} in {{name}}',
|
||||
self::TEMPLATE_EXTRA,
|
||||
)]
|
||||
#[Template(
|
||||
'Must have keys {{keys}}',
|
||||
'',
|
||||
self::TEMPLATE_STRUCTURE,
|
||||
)]
|
||||
#[Template(
|
||||
'Must not have keys {{extraKeys}}',
|
||||
'',
|
||||
self::TEMPLATE_STRUCTURE_EXTRA,
|
||||
)]
|
||||
final class KeySet extends AbstractWrapper implements NonNegatable
|
||||
final class KeySet extends Wrapper
|
||||
{
|
||||
public const TEMPLATE_NONE = '__none__';
|
||||
public const TEMPLATE_SOME = '__some__';
|
||||
public const TEMPLATE_STRUCTURE = '__structure__';
|
||||
public const TEMPLATE_STRUCTURE_EXTRA = '__structure_extra__';
|
||||
use CanBindEvaluateRule;
|
||||
use CanExtractRules;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
public const TEMPLATE_MISSING = '__missing__';
|
||||
public const TEMPLATE_EXTRA = '__extra__';
|
||||
|
||||
/** @var array<string|int> */
|
||||
private readonly array $keys;
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private array $extraKeys = [];
|
||||
|
||||
/**
|
||||
* @var Key[]
|
||||
*/
|
||||
private readonly array $keyRules;
|
||||
|
||||
public function __construct(Validatable ...$validatables)
|
||||
public function __construct(Validatable ...$rules)
|
||||
{
|
||||
$this->keyRules = array_map([$this, 'getKeyRule'], $validatables);
|
||||
$this->keys = array_map([$this, 'getKeyReference'], $this->keyRules);
|
||||
/** @var array<Key> $keyRules */
|
||||
$keyRules = $this->extractMany($rules, Key::class);
|
||||
|
||||
parent::__construct(new AllOf(...$this->keyRules));
|
||||
$this->keys = array_map(static fn(Key $rule) => $rule->getReference(), $keyRules);
|
||||
|
||||
parent::__construct(new AllOf(...$keyRules));
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
if (!$this->hasValidStructure($input)) {
|
||||
throw $this->reportError($input);
|
||||
$result = $this->bindEvaluate(new ArrayType(), $this, $input);
|
||||
if (!$result->isValid) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
parent::assert($input);
|
||||
}
|
||||
$inputKeys = array_keys($input);
|
||||
|
||||
public function check(mixed $input): void
|
||||
{
|
||||
if (!$this->hasValidStructure($input)) {
|
||||
throw $this->reportError($input);
|
||||
$missingKeys = array_diff($this->keys, $inputKeys);
|
||||
if (count($missingKeys) > 0) {
|
||||
return Result::failed($input, $this, self::TEMPLATE_MISSING)
|
||||
->withParameters(['missingKeys' => array_values($missingKeys)]);
|
||||
}
|
||||
|
||||
parent::check($input);
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
if (!$this->hasValidStructure($input)) {
|
||||
return false;
|
||||
$extraKeys = array_diff($inputKeys, $this->keys);
|
||||
if (count($extraKeys) > 0) {
|
||||
return Result::failed($input, $this, self::TEMPLATE_EXTRA)
|
||||
->withParameters(['extraKeys' => array_values($extraKeys)]);
|
||||
}
|
||||
|
||||
return parent::validate($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return [
|
||||
'keys' => $this->keys,
|
||||
'extraKeys' => $this->extraKeys,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getStandardTemplate(mixed $input): string
|
||||
{
|
||||
if (count($this->extraKeys)) {
|
||||
return self::TEMPLATE_STRUCTURE_EXTRA;
|
||||
}
|
||||
|
||||
return KeySet::TEMPLATE_STRUCTURE;
|
||||
}
|
||||
|
||||
private function getKeyRule(Validatable $validatable): Key
|
||||
{
|
||||
if ($validatable instanceof Key) {
|
||||
return $validatable;
|
||||
}
|
||||
|
||||
if (!$validatable instanceof AllOf || count($validatable->getRules()) !== 1) {
|
||||
throw new ComponentException('KeySet rule accepts only Key rules');
|
||||
}
|
||||
|
||||
return $this->getKeyRule(current($validatable->getRules()));
|
||||
}
|
||||
|
||||
private function getKeyReference(Key $rule): mixed
|
||||
{
|
||||
return $rule->getReference();
|
||||
}
|
||||
|
||||
private function hasValidStructure(mixed $input): bool
|
||||
{
|
||||
if (!is_array($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->keyRules as $keyRule) {
|
||||
if (!array_key_exists($keyRule->getReference(), $input) && $keyRule->isMandatory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($input[$keyRule->getReference()]);
|
||||
}
|
||||
|
||||
foreach ($input as $extraKey => &$ignoreValue) {
|
||||
$this->extraKeys[] = $extraKey;
|
||||
}
|
||||
|
||||
return count($input) == 0;
|
||||
return parent::evaluate($input);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
|
||||
#[Template(
|
||||
'The value must be nullable',
|
||||
|
@ -21,43 +22,20 @@ use Respect\Validation\Message\Template;
|
|||
'{{name}} must not be null',
|
||||
self::TEMPLATE_NAMED,
|
||||
)]
|
||||
final class Nullable extends AbstractWrapper
|
||||
final class Nullable extends Wrapper
|
||||
{
|
||||
public const TEMPLATE_NAMED = '__named__';
|
||||
|
||||
public function assert(mixed $input): void
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
if ($input === null) {
|
||||
return;
|
||||
if ($input !== null) {
|
||||
return parent::evaluate($input);
|
||||
}
|
||||
|
||||
parent::assert($input);
|
||||
}
|
||||
|
||||
public function check(mixed $input): void
|
||||
{
|
||||
if ($input === null) {
|
||||
return;
|
||||
if ($this->getName()) {
|
||||
return Result::passed($input, $this, self::TEMPLATE_NAMED);
|
||||
}
|
||||
|
||||
parent::check($input);
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
if ($input === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::validate($input);
|
||||
}
|
||||
|
||||
protected function getStandardTemplate(mixed $input): string
|
||||
{
|
||||
if ($input || $this->getName()) {
|
||||
return self::TEMPLATE_NAMED;
|
||||
}
|
||||
|
||||
return self::TEMPLATE_STANDARD;
|
||||
return Result::passed($input, $this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ use Respect\Validation\Result;
|
|||
'{{name}} must not be optional',
|
||||
self::TEMPLATE_NAMED,
|
||||
)]
|
||||
final class Optional extends AbstractWrapper
|
||||
final class Optional extends Wrapper
|
||||
{
|
||||
use CanValidateUndefined;
|
||||
|
||||
|
@ -31,46 +31,14 @@ final class Optional extends AbstractWrapper
|
|||
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
if ($this->isUndefined($input)) {
|
||||
return Result::passed($input, $this);
|
||||
if (!$this->isUndefined($input)) {
|
||||
return parent::evaluate($input);
|
||||
}
|
||||
|
||||
return parent::evaluate($input);
|
||||
}
|
||||
|
||||
public function assert(mixed $input): void
|
||||
{
|
||||
if ($this->isUndefined($input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::assert($input);
|
||||
}
|
||||
|
||||
public function check(mixed $input): void
|
||||
{
|
||||
if ($this->isUndefined($input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::check($input);
|
||||
}
|
||||
|
||||
public function validate(mixed $input): bool
|
||||
{
|
||||
if ($this->isUndefined($input)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::validate($input);
|
||||
}
|
||||
|
||||
protected function getStandardTemplate(mixed $input): string
|
||||
{
|
||||
if ($this->getName()) {
|
||||
return self::TEMPLATE_NAMED;
|
||||
return Result::passed($input, $this, self::TEMPLATE_NAMED);
|
||||
}
|
||||
|
||||
return self::TEMPLATE_STANDARD;
|
||||
return Result::passed($input, $this);
|
||||
}
|
||||
}
|
||||
|
|
53
library/Rules/Wrapper.php
Normal file
53
library/Rules/Wrapper.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Validation\Rules;
|
||||
|
||||
use Respect\Validation\Helpers\DeprecatedValidatableMethods;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validatable;
|
||||
|
||||
abstract class Wrapper implements Validatable
|
||||
{
|
||||
use DeprecatedValidatableMethods;
|
||||
|
||||
public function __construct(
|
||||
private readonly Validatable $rule
|
||||
) {
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
{
|
||||
return $this->rule->evaluate($input);
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->rule->getName();
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->rule->setName($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTemplate(): ?string
|
||||
{
|
||||
return $this->rule->getTemplate();
|
||||
}
|
||||
|
||||
public function setTemplate(string $template): static
|
||||
{
|
||||
$this->rule->setTemplate($template);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
48
tests/integration/rules/keySet.phpt
Normal file
48
tests/integration/rules/keySet.phpt
Normal file
|
@ -0,0 +1,48 @@
|
|||
--FILE--
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Respect\Validation\Validator as v;
|
||||
|
||||
$input = ['foo' => 42, 'bar' => 'String'];
|
||||
|
||||
$missingKeys = v::create()
|
||||
->keySet(
|
||||
v::key('foo', v::intVal()),
|
||||
v::key('bar', v::stringType()),
|
||||
v::key('baz', v::boolType())
|
||||
);
|
||||
$extraKeys = v::create()
|
||||
->keySet(
|
||||
v::key('foo', v::intVal()),
|
||||
v::key('bar', v::stringType()),
|
||||
);
|
||||
$correctStructure = v::create()
|
||||
->keySet(
|
||||
v::key('foo', v::stringType()),
|
||||
v::key('bar', v::intType()),
|
||||
);
|
||||
|
||||
exceptionMessage(static fn() => $missingKeys->assert(false));
|
||||
exceptionMessage(static fn() => $missingKeys->assert($input));
|
||||
exceptionMessage(static fn() => $extraKeys->assert($input + ['baz' => true]));
|
||||
exceptionMessage(static fn() => $correctStructure->assert($input));
|
||||
exceptionFullMessage(static fn() => $missingKeys->assert(false));
|
||||
exceptionFullMessage(static fn() => $missingKeys->assert($input));
|
||||
exceptionFullMessage(static fn() => $extraKeys->assert($input + ['baz' => true]));
|
||||
exceptionFullMessage(static fn() => $correctStructure->assert($input));
|
||||
?>
|
||||
--EXPECT--
|
||||
`false` must be of type array
|
||||
Must have keys `["baz"]` in `["foo": 42, "bar": "String"]`
|
||||
Must not have keys `["baz"]` in `["foo": 42, "bar": "String", "baz": true]`
|
||||
foo must be of type string
|
||||
- `false` must be of type array
|
||||
- Must have keys `["baz"]` in `["foo": 42, "bar": "String"]`
|
||||
- Must not have keys `["baz"]` in `["foo": 42, "bar": "String", "baz": true]`
|
||||
- All of the required rules must pass for `["foo": 42, "bar": "String"]`
|
||||
- foo must be of type string
|
||||
- bar must be of type integer
|
|
@ -9,8 +9,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Test\Rules;
|
||||
|
||||
use Respect\Validation\Rules\AbstractWrapper;
|
||||
use Respect\Validation\Rules\Wrapper;
|
||||
|
||||
final class WrapperStub extends AbstractWrapper
|
||||
final class WrapperStub extends Wrapper
|
||||
{
|
||||
}
|
||||
|
|
|
@ -10,184 +10,27 @@ 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\Exceptions\ComponentException;
|
||||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Respect\Validation\Test\Rules\Stub;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
use stdClass;
|
||||
|
||||
use function Respect\Stringifier\stringify;
|
||||
use function sprintf;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
|
||||
#[Group('rule')]
|
||||
#[CoversClass(KeySet::class)]
|
||||
final class KeySetTest extends TestCase
|
||||
final class KeySetTest extends RuleTestCase
|
||||
{
|
||||
#[Test]
|
||||
public function shouldNotAcceptAllOfWithMoreThanOneKeyRule(): void
|
||||
/** @return iterable<string, array{KeySet, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
$this->expectException(ComponentException::class);
|
||||
$this->expectExceptionMessage('KeySet rule accepts only Key rules');
|
||||
|
||||
new KeySet(new AllOf(
|
||||
new Key('foo', Stub::daze(), false),
|
||||
new Key('bar', Stub::daze(), false),
|
||||
));
|
||||
yield 'correct keys, without rule' => [new KeySet(new Key('foo')), ['foo' => 'bar']];
|
||||
yield 'correct keys, with passing rule' => [new KeySet(new Key('foo', Stub::pass(1))), ['foo' => 'bar']];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldNotAcceptAllOfWithNonKeyRule(): void
|
||||
/** @return iterable<string, array{KeySet, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
{
|
||||
$this->expectException(ComponentException::class);
|
||||
$this->expectExceptionMessage('KeySet rule accepts only Key rules');
|
||||
|
||||
new KeySet(new AllOf(Stub::daze()));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldNotAcceptNonKeyRule(): void
|
||||
{
|
||||
$this->expectException(ComponentException::class);
|
||||
$this->expectExceptionMessage('KeySet rule accepts only Key rules');
|
||||
|
||||
new KeySet(Stub::daze());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldValidateKeysWhenThereAreMissingRequiredKeys(): void
|
||||
{
|
||||
$input = [
|
||||
'foo' => 42,
|
||||
];
|
||||
|
||||
$sut = new KeySet(
|
||||
new Key('foo', Stub::pass(1), true),
|
||||
new Key('bar', Stub::daze(), true),
|
||||
);
|
||||
|
||||
self::assertFalse($sut->validate($input));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldValidateKeysWhenThereAreMissingNonRequiredKeys(): void
|
||||
{
|
||||
$input = [
|
||||
'foo' => 42,
|
||||
];
|
||||
|
||||
$sut = new KeySet(
|
||||
new Key('foo', Stub::pass(1), true),
|
||||
new Key('bar', Stub::daze(), false),
|
||||
);
|
||||
|
||||
self::assertTrue($sut->validate($input));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldValidateKeysWhenThereAreMoreKeys(): void
|
||||
{
|
||||
$input = [
|
||||
'foo' => 42,
|
||||
'bar' => 'String',
|
||||
'baz' => false,
|
||||
];
|
||||
|
||||
$sut = new KeySet(
|
||||
new Key('foo', Stub::pass(1), false),
|
||||
new Key('bar', Stub::pass(1), false),
|
||||
);
|
||||
|
||||
self::assertFalse($sut->validate($input));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldValidateKeysWhenEmpty(): void
|
||||
{
|
||||
$sut = new KeySet(
|
||||
new Key('foo', Stub::daze(), true),
|
||||
new Key('bar', Stub::daze(), true),
|
||||
);
|
||||
|
||||
self::assertFalse($sut->validate([]));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldCheckKeys(): void
|
||||
{
|
||||
$sut = new KeySet(
|
||||
new Key('foo', Stub::pass(1), true),
|
||||
new Key('bar', Stub::pass(1), true),
|
||||
);
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage(sprintf('Must have keys %s', stringify(['foo', 'bar'])));
|
||||
|
||||
$sut->check([]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldAssertKeys(): void
|
||||
{
|
||||
$sut = new KeySet(
|
||||
new Key('foo', Stub::pass(1), true),
|
||||
new Key('bar', Stub::pass(1), true),
|
||||
);
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage(sprintf('Must have keys %s', stringify(['foo', 'bar'])));
|
||||
|
||||
$sut->assert([]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldWarnOfExtraKeysWithMessage(): void
|
||||
{
|
||||
$input = ['foo' => 123, 'bar' => 456];
|
||||
|
||||
$sut = new KeySet(new Key('foo', Stub::pass(1), true));
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage(sprintf('Must not have keys %s', stringify(['bar'])));
|
||||
|
||||
$sut->assert($input);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function cannotBeNegated(): void
|
||||
{
|
||||
$key1 = new Key('foo', Stub::pass(1), true);
|
||||
|
||||
$this->expectException(ComponentException::class);
|
||||
$this->expectExceptionMessage('"Respect\Validation\Rules\KeySet" can not be wrapped in Not()');
|
||||
|
||||
new Not(new KeySet($key1));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('providerForInvalidArguments')]
|
||||
public function shouldThrowExceptionInCaseArgumentIsAnythingOtherThanArray(mixed $input): void
|
||||
{
|
||||
$sut = new KeySet(new Key('name'));
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
$this->expectExceptionMessage(sprintf('Must have keys %s', stringify(['name'])));
|
||||
|
||||
$sut->assert($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[][]
|
||||
*/
|
||||
public static function providerForInvalidArguments(): array
|
||||
{
|
||||
return [
|
||||
[''],
|
||||
[null],
|
||||
[0],
|
||||
[new stdClass()],
|
||||
];
|
||||
yield 'not array' => [new KeySet(new Key('foo')), null];
|
||||
yield 'missing keys' => [new KeySet(new Key('foo')), []];
|
||||
yield 'extra keys' => [new KeySet(new Key('foo')), ['foo' => 'bar', 'baz' => 'qux']];
|
||||
yield 'correct keys, with failing rule' => [new KeySet(new Key('foo', Stub::fail(1))), ['foo' => 'bar']];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,97 +10,75 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Rules;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\Rules\Stub;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
use stdClass;
|
||||
|
||||
#[Group('rule')]
|
||||
#[CoversClass(Nullable::class)]
|
||||
final class NullableTest extends TestCase
|
||||
final class NullableTest extends RuleTestCase
|
||||
{
|
||||
#[Test]
|
||||
public function shouldNotValidateRuleWhenInputIsNull(): void
|
||||
public function itShouldUseStandardTemplateWhenItHasNameWhenInputIsOptional(): void
|
||||
{
|
||||
$rule = new Nullable(Stub::pass(1));
|
||||
|
||||
self::assertTrue($rule->validate(null));
|
||||
$result = $rule->evaluate(null);
|
||||
|
||||
self::assertSame($rule, $result->rule);
|
||||
self::assertSame(Nullable::TEMPLATE_STANDARD, $result->template);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('providerForNotNullable')]
|
||||
public function shouldValidateRuleWhenInputIsNotNullable(mixed $input): void
|
||||
public function itShouldUseNamedTemplateWhenItHasNameWhenInputIsNullable(): void
|
||||
{
|
||||
$rule = new Nullable(Stub::pass(1));
|
||||
$rule->setName('foo');
|
||||
|
||||
self::assertTrue($rule->validate($input));
|
||||
$result = $rule->evaluate(null);
|
||||
|
||||
self::assertSame($rule, $result->rule);
|
||||
self::assertSame(Nullable::TEMPLATE_NAMED, $result->template);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DoesNotPerformAssertions]
|
||||
public function shouldNotAssertRuleWhenInputIsNull(): void
|
||||
public function itShouldUseWrappedRuleToEvaluateWhenNotNullable(): void
|
||||
{
|
||||
$sut = new Nullable(Stub::daze());
|
||||
$sut->assert(null);
|
||||
$input = new stdClass();
|
||||
|
||||
$wrapped = Stub::pass(2);
|
||||
$rule = new Nullable($wrapped);
|
||||
|
||||
self::assertEquals($wrapped->evaluate($input), $rule->evaluate($input));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('providerForNotNullable')]
|
||||
public function shouldAssertRuleWhenInputIsNotNullable(mixed $input): void
|
||||
/** @return iterable<string, array{Nullable, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
$rule = Stub::pass(1);
|
||||
|
||||
$sut = new Nullable($rule);
|
||||
$sut->assert($input);
|
||||
|
||||
self::assertSame([$input], $rule->inputs);
|
||||
yield 'null' => [new Nullable(Stub::daze()), null];
|
||||
yield 'not null with passing rule' => [new Nullable(Stub::pass(1)), 42];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DoesNotPerformAssertions]
|
||||
public function shouldNotCheckRuleWhenInputIsNull(): void
|
||||
/** @return iterable<array{Nullable, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
{
|
||||
$rule = new Nullable(Stub::daze());
|
||||
$rule->check(null);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('providerForNotNullable')]
|
||||
public function shouldCheckRuleWhenInputIsNotNullable(mixed $input): void
|
||||
{
|
||||
$rule = Stub::pass(1);
|
||||
|
||||
$sut = new Nullable($rule);
|
||||
$sut->check($input);
|
||||
|
||||
self::assertSame([$input], $rule->inputs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[][]
|
||||
*/
|
||||
public static function providerForNotNullable(): array
|
||||
{
|
||||
return [
|
||||
[''],
|
||||
[1],
|
||||
[[]],
|
||||
[' '],
|
||||
[0],
|
||||
['0'],
|
||||
[0],
|
||||
['0.0'],
|
||||
[false],
|
||||
[['']],
|
||||
[[' ']],
|
||||
[[0]],
|
||||
[['0']],
|
||||
[[false]],
|
||||
[[[''], [0]]],
|
||||
[new stdClass()],
|
||||
];
|
||||
yield [new Nullable(Stub::fail(1)), ''];
|
||||
yield [new Nullable(Stub::fail(1)), 1];
|
||||
yield [new Nullable(Stub::fail(1)), []];
|
||||
yield [new Nullable(Stub::fail(1)), ' '];
|
||||
yield [new Nullable(Stub::fail(1)), 0];
|
||||
yield [new Nullable(Stub::fail(1)), '0'];
|
||||
yield [new Nullable(Stub::fail(1)), 0];
|
||||
yield [new Nullable(Stub::fail(1)), '0.0'];
|
||||
yield [new Nullable(Stub::fail(1)), false];
|
||||
yield [new Nullable(Stub::fail(1)), ['']];
|
||||
yield [new Nullable(Stub::fail(1)), [' ']];
|
||||
yield [new Nullable(Stub::fail(1)), [0]];
|
||||
yield [new Nullable(Stub::fail(1)), ['0']];
|
||||
yield [new Nullable(Stub::fail(1)), [false]];
|
||||
yield [new Nullable(Stub::fail(1)), [[''], [0]]];
|
||||
yield [new Nullable(Stub::fail(1)), new stdClass()];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,137 +10,75 @@ 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\Test\Rules\Stub;
|
||||
use Respect\Validation\Test\RuleTestCase;
|
||||
use stdClass;
|
||||
|
||||
#[Group('rule')]
|
||||
#[CoversClass(Optional::class)]
|
||||
final class OptionalTest extends TestCase
|
||||
final class OptionalTest extends RuleTestCase
|
||||
{
|
||||
#[Test]
|
||||
#[DataProvider('providerForOptional')]
|
||||
public function shouldNotValidateRuleWhenInputIsOptional(mixed $input): void
|
||||
public function itShouldUseStandardTemplateWhenItHasNameWhenInputIsOptional(): void
|
||||
{
|
||||
$validatable = $this->createMock(Validatable::class);
|
||||
$validatable
|
||||
->expects(self::never())
|
||||
->method('validate');
|
||||
$rule = new Optional(Stub::pass(1));
|
||||
|
||||
$rule = new Optional($validatable);
|
||||
$result = $rule->evaluate('');
|
||||
|
||||
self::assertTrue($rule->validate($input));
|
||||
self::assertSame($rule, $result->rule);
|
||||
self::assertSame(Optional::TEMPLATE_STANDARD, $result->template);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('providerForNotOptional')]
|
||||
public function shouldValidateRuleWhenInputIsNotOptional(mixed $input): void
|
||||
public function itShouldUseNamedTemplateWhenItHasNameWhenInputIsOptional(): void
|
||||
{
|
||||
$validatable = $this->createMock(Validatable::class);
|
||||
$validatable
|
||||
->expects(self::once())
|
||||
->method('validate')
|
||||
->with($input)
|
||||
->willReturn(true);
|
||||
$rule = new Optional(Stub::pass(1));
|
||||
$rule->setName('foo');
|
||||
|
||||
$rule = new Optional($validatable);
|
||||
$result = $rule->evaluate('');
|
||||
|
||||
self::assertTrue($rule->validate($input));
|
||||
self::assertSame($rule, $result->rule);
|
||||
self::assertSame(Optional::TEMPLATE_NAMED, $result->template);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldNotAssertRuleWhenInputIsOptional(): void
|
||||
public function itShouldUseWrappedRuleToEvaluateWhenNotOptional(): void
|
||||
{
|
||||
$validatable = $this->createMock(Validatable::class);
|
||||
$validatable
|
||||
->expects(self::never())
|
||||
->method('assert');
|
||||
$input = new stdClass();
|
||||
|
||||
$rule = new Optional($validatable);
|
||||
$wrapped = Stub::pass(2);
|
||||
$rule = new Optional($wrapped);
|
||||
|
||||
$rule->assert('');
|
||||
self::assertEquals($wrapped->evaluate($input), $rule->evaluate($input));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldAssertRuleWhenInputIsNotOptional(): void
|
||||
/** @return iterable<string, array{Optional, mixed}> */
|
||||
public static function providerForValidInput(): iterable
|
||||
{
|
||||
$input = 'foo';
|
||||
|
||||
$validatable = $this->createMock(Validatable::class);
|
||||
$validatable
|
||||
->expects(self::once())
|
||||
->method('assert')
|
||||
->with($input);
|
||||
|
||||
$rule = new Optional($validatable);
|
||||
|
||||
$rule->assert($input);
|
||||
yield 'null' => [new Optional(Stub::daze()), null];
|
||||
yield 'empty string' => [new Optional(Stub::daze()), ''];
|
||||
yield 'not optional' => [new Optional(Stub::pass(1)), 42];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldNotCheckRuleWhenInputIsOptional(): void
|
||||
/** @return iterable<array{Optional, mixed}> */
|
||||
public static function providerForInvalidInput(): iterable
|
||||
{
|
||||
$validatable = $this->createMock(Validatable::class);
|
||||
$validatable
|
||||
->expects(self::never())
|
||||
->method('check');
|
||||
|
||||
$rule = new Optional($validatable);
|
||||
|
||||
$rule->check('');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldCheckRuleWhenInputIsNotOptional(): void
|
||||
{
|
||||
$input = 'foo';
|
||||
|
||||
$validatable = $this->createMock(Validatable::class);
|
||||
$validatable
|
||||
->expects(self::once())
|
||||
->method('check')
|
||||
->with($input);
|
||||
|
||||
$rule = new Optional($validatable);
|
||||
|
||||
$rule->check($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[][]
|
||||
*/
|
||||
public static function providerForOptional(): array
|
||||
{
|
||||
return [
|
||||
[null],
|
||||
[''],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[][]
|
||||
*/
|
||||
public static function providerForNotOptional(): array
|
||||
{
|
||||
return [
|
||||
[1],
|
||||
[[]],
|
||||
[' '],
|
||||
[0],
|
||||
['0'],
|
||||
[0],
|
||||
['0.0'],
|
||||
[false],
|
||||
[['']],
|
||||
[[' ']],
|
||||
[[0]],
|
||||
[['0']],
|
||||
[[false]],
|
||||
[[[''], [0]]],
|
||||
[new stdClass()],
|
||||
];
|
||||
yield [new Optional(Stub::fail(1)), 1];
|
||||
yield [new Optional(Stub::fail(1)), []];
|
||||
yield [new Optional(Stub::fail(1)), ' '];
|
||||
yield [new Optional(Stub::fail(1)), 0];
|
||||
yield [new Optional(Stub::fail(1)), '0'];
|
||||
yield [new Optional(Stub::fail(1)), 0];
|
||||
yield [new Optional(Stub::fail(1)), '0.0'];
|
||||
yield [new Optional(Stub::fail(1)), false];
|
||||
yield [new Optional(Stub::fail(1)), ['']];
|
||||
yield [new Optional(Stub::fail(1)), [' ']];
|
||||
yield [new Optional(Stub::fail(1)), [0]];
|
||||
yield [new Optional(Stub::fail(1)), ['0']];
|
||||
yield [new Optional(Stub::fail(1)), [false]];
|
||||
yield [new Optional(Stub::fail(1)), [[''], [0]]];
|
||||
yield [new Optional(Stub::fail(1)), new stdClass()];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,37 +12,24 @@ namespace Respect\Validation\Rules;
|
|||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Exceptions\ValidationException;
|
||||
use Respect\Validation\Test\Rules\Stub;
|
||||
use Respect\Validation\Test\Rules\WrapperStub;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
|
||||
#[Group('core')]
|
||||
#[CoversClass(AbstractWrapper::class)]
|
||||
final class AbstractWrapperTest extends TestCase
|
||||
#[CoversClass(Wrapper::class)]
|
||||
final class WrapperTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function shouldUseWrappedToValidate(): void
|
||||
public function shouldUseWrappedToEvaluate(): void
|
||||
{
|
||||
$sut = new WrapperStub(Stub::pass(1));
|
||||
$wrapped = Stub::pass(2);
|
||||
|
||||
self::assertTrue($sut->validate('Whatever'));
|
||||
}
|
||||
$wrapper = new WrapperStub($wrapped);
|
||||
|
||||
#[Test]
|
||||
public function shouldUseWrappedToAssert(): void
|
||||
{
|
||||
$sut = new WrapperStub(Stub::fail(1));
|
||||
$this->expectException(ValidationException::class);
|
||||
$sut->assert('Whatever');
|
||||
}
|
||||
$input = 'Whatever';
|
||||
|
||||
#[Test]
|
||||
public function shouldUseWrappedToCheck(): void
|
||||
{
|
||||
$sut = new WrapperStub(Stub::fail(1));
|
||||
$this->expectException(ValidationException::class);
|
||||
$sut->check('Whatever');
|
||||
self::assertEquals($wrapped->evaluate($input), $wrapper->evaluate($input));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
|
@ -56,5 +43,20 @@ final class AbstractWrapperTest extends TestCase
|
|||
$sut->setName($name);
|
||||
|
||||
self::assertSame($name, $rule->getName());
|
||||
self::assertSame($name, $sut->getName());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function shouldPassTemplateOnToWrapped(): void
|
||||
{
|
||||
$template = 'Whatever';
|
||||
|
||||
$rule = Stub::pass(1);
|
||||
|
||||
$sut = new WrapperStub($rule);
|
||||
$sut->setTemplate($template);
|
||||
|
||||
self::assertSame($template, $rule->getTemplate());
|
||||
self::assertSame($template, $sut->getTemplate());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue