diff --git a/docs/rules/AllOf.md b/docs/rules/AllOf.md index 22648e880..9133142a5 100644 --- a/docs/rules/AllOf.md +++ b/docs/rules/AllOf.md @@ -1,6 +1,6 @@ # AllOf -- `AllOf(Validatable ...$rule)` +- `AllOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule)` Will validate if all inner validators validates. @@ -17,6 +17,7 @@ v::allOf(v::intVal(), v::positive())->validate(15); // true Version | Description --------|------------- + 3.0.0 | Require at least two rules to be passed 0.3.9 | Created *** diff --git a/docs/rules/AnyOf.md b/docs/rules/AnyOf.md index 3d03463ba..a5f40cdef 100644 --- a/docs/rules/AnyOf.md +++ b/docs/rules/AnyOf.md @@ -1,6 +1,6 @@ # AnyOf -- `AnyOf(Validatable ...$rule)` +- `AnyOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule)` This is a group validator that acts as an OR operator. @@ -22,6 +22,7 @@ so `AnyOf()` returns true. Version | Description --------|------------- + 3.0.0 | Require at least two rules to be passed 2.0.0 | Created *** diff --git a/docs/rules/KeySet.md b/docs/rules/KeySet.md index ca46bf2a2..1748c1be3 100644 --- a/docs/rules/KeySet.md +++ b/docs/rules/KeySet.md @@ -1,6 +1,6 @@ # KeySet -- `KeySet(Key ...$rule)` +- `KeySet(Key $rule, Key ...$rules)` Validates a keys in a defined structure. @@ -57,6 +57,7 @@ The keys' order is not considered in the validation. Version | Description --------|------------- + 3.0.0 | Require at one rule to be passed 2.3.0 | KeySet is NonNegatable, fixed message with extra keys 1.0.0 | Created diff --git a/docs/rules/NoneOf.md b/docs/rules/NoneOf.md index cc4475a0b..db06177be 100644 --- a/docs/rules/NoneOf.md +++ b/docs/rules/NoneOf.md @@ -1,6 +1,6 @@ # NoneOf -- `NoneOf(Validatable ...$rule)` +- `NoneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule)` Validates if NONE of the given validators validate: @@ -22,6 +22,7 @@ In the sample above, 'foo' isn't a integer nor a float, so noneOf returns true. Version | Description --------|------------- + 3.0.0 | Require at least two rules to be passed 0.3.9 | Created *** diff --git a/docs/rules/OneOf.md b/docs/rules/OneOf.md index 4d24e7e7b..debc65c58 100644 --- a/docs/rules/OneOf.md +++ b/docs/rules/OneOf.md @@ -1,6 +1,6 @@ # OneOf -- `OneOf(Validatable ...$rule)` +- `OneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule)` Will validate if exactly one inner validator passes. @@ -23,7 +23,7 @@ character, one or the other, but not neither nor both. Version | Description --------|------------- - 2.0.0 | Changed to pass if only one inner validator passes + 3.0.0 | Require at least two rules to be passed 0.3.9 | Created *** diff --git a/library/ChainedValidator.php b/library/ChainedValidator.php index 07d84a52b..ae40dda53 100644 --- a/library/ChainedValidator.php +++ b/library/ChainedValidator.php @@ -10,11 +10,10 @@ namespace Respect\Validation; use finfo; -use Respect\Validation\Rules\Key; interface ChainedValidator extends Validatable { - public function allOf(Validatable ...$rule): ChainedValidator; + public function allOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator; public function alnum(string ...$additionalChars): ChainedValidator; @@ -24,7 +23,7 @@ public function alwaysInvalid(): ChainedValidator; public function alwaysValid(): ChainedValidator; - public function anyOf(Validatable ...$rule): ChainedValidator; + public function anyOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator; public function arrayType(): ChainedValidator; @@ -66,9 +65,7 @@ public function consonant(string ...$additionalChars): ChainedValidator; public function contains(mixed $containsValue, bool $identical = false): ChainedValidator; - /** - * @param mixed[] $needles - */ + /** @param non-empty-array $needles */ public function containsAny(array $needles, bool $strictCompareArray = false): ChainedValidator; public function countable(): ChainedValidator; @@ -176,7 +173,7 @@ public function keyNested( bool $mandatory = true ): ChainedValidator; - public function keySet(Key ...$rule): ChainedValidator; + public function keySet(Validatable $rule, Validatable ...$rules): ChainedValidator; public function lazyConsecutive(callable $ruleCreator, callable ...$ruleCreators): ChainedValidator; @@ -219,7 +216,7 @@ public function nip(): ChainedValidator; public function no(bool $useLocale = false): ChainedValidator; - public function noneOf(Validatable ...$rule): ChainedValidator; + public function noneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator; public function not(Validatable $rule): ChainedValidator; @@ -245,7 +242,7 @@ public function objectType(): ChainedValidator; public function odd(): ChainedValidator; - public function oneOf(Validatable ...$rule): ChainedValidator; + public function oneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator; public function optional(Validatable $rule): ChainedValidator; diff --git a/library/Rules/AbstractComposite.php b/library/Rules/AbstractComposite.php deleted file mode 100644 index 756dcbd9d..000000000 --- a/library/Rules/AbstractComposite.php +++ /dev/null @@ -1,121 +0,0 @@ - - * SPDX-License-Identifier: MIT - */ - -declare(strict_types=1); - -namespace Respect\Validation\Rules; - -use Respect\Validation\Exceptions\NestedValidationException; -use Respect\Validation\Exceptions\ValidationException; -use Respect\Validation\Validatable; - -abstract class AbstractComposite extends AbstractRule -{ - /** - * @var Validatable[] - */ - private array $rules = []; - - public function __construct(Validatable ...$rules) - { - $this->rules = $rules; - } - - public function setName(string $name): static - { - $parentName = $this->getName(); - foreach ($this->rules as $rule) { - $ruleName = $rule->getName(); - if ($ruleName && $parentName !== $ruleName) { - continue; - } - - $rule->setName($name); - } - - return parent::setName($name); - } - - public function addRule(Validatable $rule): self - { - if ($this->shouldHaveNameOverwritten($rule) && $this->getName() !== null) { - $rule->setName($this->getName()); - } - - $this->rules[] = $rule; - - return $this; - } - - /** - * @return Validatable[] - */ - public function getRules(): array - { - return $this->rules; - } - - public function assert(mixed $input): void - { - $exceptions = $this->getAllThrownExceptions($input); - if (empty($exceptions)) { - return; - } - - $exception = $this->reportError($input); - if ($exception instanceof NestedValidationException) { - $exception->addChildren($exceptions); - } - - throw $exception; - } - - /** - * @return ValidationException[] - */ - private function getAllThrownExceptions(mixed $input): array - { - $exceptions = []; - foreach ($this->getRules() as $rule) { - try { - $rule->assert($input); - } catch (ValidationException $exception) { - $this->updateExceptionTemplate($exception); - $exceptions[] = $exception; - } - } - - return $exceptions; - } - - private function shouldHaveNameOverwritten(Validatable $rule): bool - { - return $this->hasName($this) && !$this->hasName($rule); - } - - private function hasName(Validatable $rule): bool - { - return $rule->getName() !== null; - } - - private function updateExceptionTemplate(ValidationException $exception): void - { - if ($this->getTemplate() === null || $exception->hasCustomTemplate()) { - return; - } - - $exception->updateTemplate($this->getTemplate()); - - if (!$exception instanceof NestedValidationException) { - return; - } - - foreach ($exception->getChildren() as $childException) { - $this->updateExceptionTemplate($childException); - } - } -} diff --git a/library/Rules/AllOf.php b/library/Rules/AllOf.php index 54af3bfa9..e6eccb63c 100644 --- a/library/Rules/AllOf.php +++ b/library/Rules/AllOf.php @@ -9,8 +9,6 @@ namespace Respect\Validation\Rules; -use Respect\Validation\Attributes\ExceptionClass; -use Respect\Validation\Exceptions\NestedValidationException; use Respect\Validation\Message\Template; use Respect\Validation\Result; use Respect\Validation\Rule; @@ -20,7 +18,6 @@ use function array_reduce; use function count; -#[ExceptionClass(NestedValidationException::class)] #[Template( 'These rules must pass for {{name}}', 'These rules must not pass for {{name}}', @@ -31,7 +28,7 @@ 'None of these rules must pass for {{name}}', self::TEMPLATE_NONE, )] -final class AllOf extends AbstractComposite +final class AllOf extends Composite { public const TEMPLATE_NONE = '__none__'; public const TEMPLATE_SOME = '__some__'; @@ -48,40 +45,4 @@ public function evaluate(mixed $input): Result return (new Result($valid, $input, $this, $template))->withChildren(...$children); } - - public function assert(mixed $input): void - { - try { - parent::assert($input); - } catch (NestedValidationException $exception) { - if (count($exception->getChildren()) === count($this->getRules()) && !$exception->hasCustomTemplate()) { - $exception->updateTemplate(self::TEMPLATE_NONE); - } - - throw $exception; - } - } - - public function check(mixed $input): void - { - foreach ($this->getRules() as $rule) { - $rule->check($input); - } - } - - public function validate(mixed $input): bool - { - foreach ($this->getRules() as $rule) { - if (!$rule->validate($input)) { - return false; - } - } - - return true; - } - - protected function getStandardTemplate(mixed $input): string - { - return self::TEMPLATE_SOME; - } } diff --git a/library/Rules/AnyOf.php b/library/Rules/AnyOf.php index d998b4f75..7ab631244 100644 --- a/library/Rules/AnyOf.php +++ b/library/Rules/AnyOf.php @@ -9,23 +9,18 @@ namespace Respect\Validation\Rules; -use Respect\Validation\Attributes\ExceptionClass; -use Respect\Validation\Exceptions\NestedValidationException; -use Respect\Validation\Exceptions\ValidationException; use Respect\Validation\Message\Template; use Respect\Validation\Result; use Respect\Validation\Rule; use function array_map; use function array_reduce; -use function count; -#[ExceptionClass(NestedValidationException::class)] #[Template( 'At least one of these rules must pass for {{name}}', 'At least one of these rules must not pass for {{name}}', )] -final class AnyOf extends AbstractComposite +final class AnyOf extends Composite { public function evaluate(mixed $input): Result { @@ -34,47 +29,4 @@ public function evaluate(mixed $input): Result return (new Result($valid, $input, $this))->withChildren(...$children); } - - public function assert(mixed $input): void - { - try { - parent::assert($input); - } catch (NestedValidationException $exception) { - if (count($exception->getChildren()) === count($this->getRules())) { - throw $exception; - } - } - } - - public function validate(mixed $input): bool - { - foreach ($this->getRules() as $v) { - if ($v->validate($input)) { - return true; - } - } - - return false; - } - - public function check(mixed $input): void - { - foreach ($this->getRules() as $v) { - try { - $v->check($input); - - return; - } catch (ValidationException $e) { - if (!isset($firstException)) { - $firstException = $e; - } - } - } - - if (isset($firstException)) { - throw $firstException; - } - - throw $this->reportError($input); - } } diff --git a/library/Rules/Composite.php b/library/Rules/Composite.php new file mode 100644 index 000000000..f92153e1d --- /dev/null +++ b/library/Rules/Composite.php @@ -0,0 +1,70 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Rules; + +use Respect\Validation\Helpers\DeprecatedValidatableMethods; +use Respect\Validation\Validatable; + +use function array_merge; + +abstract class Composite implements Validatable +{ + use DeprecatedValidatableMethods; + + /** @var non-empty-array */ + private readonly array $rules; + + private ?string $name = null; + + private ?string $template = null; + + public function __construct(Validatable $rule1, Validatable $rule2, Validatable ...$rules) + { + $this->rules = array_merge([$rule1, $rule2], $rules); + } + + /** @return non-empty-array */ + public function getRules(): array + { + return $this->rules; + } + + public function setName(string $name): static + { + foreach ($this->getRules() as $rule) { + if ($rule->getName() && $this->name !== $rule->getName()) { + continue; + } + + $rule->setName($name); + } + + $this->name = $name; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setTemplate(string $template): static + { + $this->template = $template; + + return $this; + } + + public function getTemplate(): ?string + { + return $this->template; + } +} diff --git a/library/Rules/ContainsAny.php b/library/Rules/ContainsAny.php index f59f861e4..4043c82f0 100644 --- a/library/Rules/ContainsAny.php +++ b/library/Rules/ContainsAny.php @@ -9,9 +9,11 @@ namespace Respect\Validation\Rules; +use Respect\Validation\Exceptions\InvalidRuleConstructorException; use Respect\Validation\Message\Template; use function array_map; +use function count; #[Template( '{{name}} must contain at least one of the values {{needles}}', @@ -20,13 +22,19 @@ final class ContainsAny extends Envelope { /** - * @param mixed[] $needles At least one of the values provided must be found in input string or array + * @param non-empty-array $needles At least one of the values provided must be found in input string or array * @param bool $identical Defines whether the value should be compared strictly, when validating array */ public function __construct(array $needles, bool $identical = false) { + // @phpstan-ignore-next-line + if (empty($needles)) { + throw new InvalidRuleConstructorException('At least one value must be provided'); + } + + $rules = $this->getRules($needles, $identical); parent::__construct( - new AnyOf(...$this->getRules($needles, $identical)), + count($rules) === 1 ? $rules[0] : new AnyOf(...$rules), ['needles' => $needles] ); } diff --git a/library/Rules/KeySet.php b/library/Rules/KeySet.php index 2060dbe4f..439188679 100644 --- a/library/Rules/KeySet.php +++ b/library/Rules/KeySet.php @@ -18,6 +18,7 @@ use function array_diff; use function array_keys; use function array_map; +use function array_merge; use function array_values; use function count; @@ -42,14 +43,14 @@ final class KeySet extends Wrapper /** @var array */ private readonly array $keys; - public function __construct(Validatable ...$rules) + public function __construct(Validatable $rule, Validatable ...$rules) { /** @var array $keyRules */ - $keyRules = $this->extractMany($rules, Key::class); + $keyRules = $this->extractMany(array_merge([$rule], $rules), Key::class); - $this->keys = array_map(static fn(Key $rule) => $rule->getReference(), $keyRules); + $this->keys = array_map(static fn(Key $keyRule) => $keyRule->getReference(), $keyRules); - parent::__construct(new AllOf(...$keyRules)); + parent::__construct(count($keyRules) === 1 ? $keyRules[0] : new AllOf(...$keyRules)); } public function evaluate(mixed $input): Result diff --git a/library/Rules/NoneOf.php b/library/Rules/NoneOf.php index 9ebabd0bf..5ca896d39 100644 --- a/library/Rules/NoneOf.php +++ b/library/Rules/NoneOf.php @@ -9,22 +9,18 @@ namespace Respect\Validation\Rules; -use Respect\Validation\Attributes\ExceptionClass; -use Respect\Validation\Exceptions\NestedValidationException; use Respect\Validation\Message\Template; use Respect\Validation\Result; use Respect\Validation\Rule; use function array_map; use function array_reduce; -use function count; -#[ExceptionClass(NestedValidationException::class)] #[Template( 'None of these rules must pass for {{name}}', 'All of these rules must pass for {{name}}', )] -final class NoneOf extends AbstractComposite +final class NoneOf extends Composite { public function evaluate(mixed $input): Result { @@ -33,26 +29,4 @@ public function evaluate(mixed $input): Result return (new Result($valid, $input, $this))->withChildren(...$children); } - - public function assert(mixed $input): void - { - try { - parent::assert($input); - } catch (NestedValidationException $exception) { - if (count($exception->getChildren()) !== count($this->getRules())) { - throw $exception; - } - } - } - - public function validate(mixed $input): bool - { - foreach ($this->getRules() as $rule) { - if ($rule->validate($input)) { - return false; - } - } - - return true; - } } diff --git a/library/Rules/OneOf.php b/library/Rules/OneOf.php index 0ff581eed..2aff288fb 100644 --- a/library/Rules/OneOf.php +++ b/library/Rules/OneOf.php @@ -9,24 +9,18 @@ namespace Respect\Validation\Rules; -use Respect\Validation\Attributes\ExceptionClass; -use Respect\Validation\Exceptions\NestedValidationException; -use Respect\Validation\Exceptions\ValidationException; use Respect\Validation\Message\Template; use Respect\Validation\Result; use Respect\Validation\Rule; use function array_map; use function array_reduce; -use function array_shift; -use function count; -#[ExceptionClass(NestedValidationException::class)] #[Template( 'Only one of these rules must pass for {{name}}', 'Only one of these rules must not pass for {{name}}', )] -final class OneOf extends AbstractComposite +final class OneOf extends Composite { public function evaluate(mixed $input): Result { @@ -35,50 +29,4 @@ public function evaluate(mixed $input): Result return (new Result($count === 1, $input, $this))->withChildren(...$children); } - - public function assert(mixed $input): void - { - try { - parent::assert($input); - } catch (NestedValidationException $exception) { - if (count($exception->getChildren()) !== count($this->getRules()) - 1) { - throw $exception; - } - } - } - - public function validate(mixed $input): bool - { - $rulesPassedCount = 0; - foreach ($this->getRules() as $rule) { - if (!$rule->validate($input)) { - continue; - } - - ++$rulesPassedCount; - } - - return $rulesPassedCount === 1; - } - - public function check(mixed $input): void - { - $exceptions = []; - $rulesPassedCount = 0; - foreach ($this->getRules() as $rule) { - try { - $rule->check($input); - - ++$rulesPassedCount; - } catch (ValidationException $exception) { - $exceptions[] = $exception; - } - } - - if ($rulesPassedCount === 1) { - return; - } - - throw array_shift($exceptions) ?: $this->reportError($input); - } } diff --git a/library/StaticValidator.php b/library/StaticValidator.php index c00e8668c..efb52e162 100644 --- a/library/StaticValidator.php +++ b/library/StaticValidator.php @@ -13,7 +13,7 @@ interface StaticValidator { - public static function allOf(Validatable ...$rule): ChainedValidator; + public static function allOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator; public static function alnum(string ...$additionalChars): ChainedValidator; @@ -23,7 +23,7 @@ public static function alwaysInvalid(): ChainedValidator; public static function alwaysValid(): ChainedValidator; - public static function anyOf(Validatable ...$rule): ChainedValidator; + public static function anyOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator; public static function arrayType(): ChainedValidator; @@ -67,9 +67,7 @@ public static function consonant(string ...$additionalChars): ChainedValidator; public static function contains(mixed $containsValue, bool $identical = false): ChainedValidator; - /** - * @param mixed[] $needles - */ + /** @param non-empty-array $needles */ public static function containsAny(array $needles, bool $strictCompareArray = false): ChainedValidator; public static function countable(): ChainedValidator; @@ -177,7 +175,7 @@ public static function keyNested( bool $mandatory = true ): ChainedValidator; - public static function keySet(Validatable ...$rule): ChainedValidator; + public static function keySet(Validatable $rule, Validatable ...$rules): ChainedValidator; public static function lazyConsecutive(callable $ruleCreator, callable ...$ruleCreators): ChainedValidator; @@ -220,7 +218,7 @@ public static function nip(): ChainedValidator; public static function no(bool $useLocale = false): ChainedValidator; - public static function noneOf(Validatable ...$rule): ChainedValidator; + public static function noneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator; public static function not(Validatable $rule): ChainedValidator; @@ -246,7 +244,7 @@ public static function objectType(): ChainedValidator; public static function odd(): ChainedValidator; - public static function oneOf(Validatable ...$rule): ChainedValidator; + public static function oneOf(Validatable $rule1, Validatable $rule2, Validatable ...$rule): ChainedValidator; public static function optional(Validatable $rule): ChainedValidator; diff --git a/tests/integration/issue-425.phpt b/tests/integration/issue-425.phpt index c17c6a455..eb08addb7 100644 --- a/tests/integration/issue-425.phpt +++ b/tests/integration/issue-425.phpt @@ -8,7 +8,7 @@ require 'vendor/autoload.php'; use Respect\Validation\Validator as v; $validator = v::create() - ->key('age', v::intType()->notEmpty()->noneOf(v::stringType())) + ->key('age', v::intType()->notEmpty()->noneOf(v::stringType(), v::arrayType())) ->key('reference', v::stringType()->notEmpty()->length(1, 50)); exceptionFullMessage(static fn() => $validator->assert(['age' => 1])); diff --git a/tests/integration/rules/allOf.phpt b/tests/integration/rules/allOf.phpt index f6d2c8ac1..9cddf4ac6 100644 --- a/tests/integration/rules/allOf.phpt +++ b/tests/integration/rules/allOf.phpt @@ -8,11 +8,10 @@ require 'vendor/autoload.php'; use Respect\Validation\Validator as v; run([ - 'Single rule' => [v::allOf(v::stringType()), 1], 'Two rules' => [v::allOf(v::intType(), v::negative()), '2'], 'Wrapped by "not"' => [v::not(v::allOf(v::intType(), v::positive())), 3], - 'Wrapping "not"' => [v::allOf(v::not(v::intType(), v::positive())), 4], - 'With a single template' => [v::allOf(v::stringType()), 5, 'This is a single template'], + 'Wrapping "not"' => [v::allOf(v::not(v::intType(), v::positive()), v::greaterThan(2)), 4], + 'With a single template' => [v::allOf(v::stringType(), v::arrayType()), 5, 'This is a single template'], 'With multiple templates' => [ v::allOf(v::stringType(), v::uppercase()), 5, @@ -25,14 +24,6 @@ run([ ]); ?> --EXPECT-- -Single rule -⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺ -1 must be of type string -- 1 must be of type string -[ - 'stringType' => '1 must be of type string', -] - Two rules ⎺⎺⎺⎺⎺⎺⎺⎺⎺ "2" must be of type integer diff --git a/tests/library/Rules/ConcreteComposite.php b/tests/library/Rules/ConcreteComposite.php new file mode 100644 index 000000000..a188295a3 --- /dev/null +++ b/tests/library/Rules/ConcreteComposite.php @@ -0,0 +1,21 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Test\Rules; + +use Respect\Validation\Result; +use Respect\Validation\Rules\Composite; + +final class ConcreteComposite extends Composite +{ + public function evaluate(mixed $input): Result + { + return Result::passed($input, $this); + } +} diff --git a/tests/library/Stubs/CompositeSub.php b/tests/library/Stubs/CompositeSub.php deleted file mode 100644 index 266658e43..000000000 --- a/tests/library/Stubs/CompositeSub.php +++ /dev/null @@ -1,39 +0,0 @@ - - * SPDX-License-Identifier: MIT - */ - -declare(strict_types=1); - -namespace Respect\Validation\Test\Stubs; - -use Respect\Validation\Message\Parameter\Stringify; -use Respect\Validation\Message\TemplateRenderer; -use Respect\Validation\Rules\AbstractComposite; -use Respect\Validation\Test\Exceptions\CompositeStubException; -use Respect\Validation\Validatable; - -final class CompositeSub extends AbstractComposite -{ - public function validate(mixed $input): bool - { - return true; - } - - /** - * @param array $extraParameters - */ - public function reportError(mixed $input, array $extraParameters = []): CompositeStubException - { - return new CompositeStubException( - input: $input, - id: 'CompositeStub', - params: $extraParameters, - template: Validatable::TEMPLATE_STANDARD, - templates: [], - formatter: new TemplateRenderer(static fn ($value) => $value, new Stringify()) - ); - } -} diff --git a/tests/unit/Rules/AbstractCompositeTest.php b/tests/unit/Rules/AbstractCompositeTest.php deleted file mode 100644 index 11a41c583..000000000 --- a/tests/unit/Rules/AbstractCompositeTest.php +++ /dev/null @@ -1,180 +0,0 @@ - - * SPDX-License-Identifier: MIT - */ - -declare(strict_types=1); - -namespace Respect\Validation\Rules; - -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\Group; -use PHPUnit\Framework\Attributes\Test; -use Respect\Validation\Test\Exceptions\CompositeStubException; -use Respect\Validation\Test\Rules\Stub; -use Respect\Validation\Test\Stubs\CompositeSub; -use Respect\Validation\Test\TestCase; - -use function current; - -#[Group('rule')] -#[CoversClass(AbstractComposite::class)] -final class AbstractCompositeTest extends TestCase -{ - #[Test] - public function itShouldUpdateTheNameOfTheChildWhenUpdatingItsName(): void - { - $ruleName = 'something'; - - $child = Stub::pass(1); - - $parent = new CompositeSub($child); - - self::assertNull($child->getName()); - - $parent->setName($ruleName); - - self::assertSame($ruleName, $child->getName()); - } - - #[Test] - public function itShouldUpdateTheNameOfTheChildWhenAddingIt(): void - { - $ruleName = 'something'; - - $rule = Stub::pass(1); - - $sut = new CompositeSub(); - $sut->setName($ruleName); - - self::assertNull($rule->getName()); - - $sut->addRule($rule); - - self::assertSame($ruleName, $rule->getName()); - } - - #[Test] - public function itShouldNotUpdateTheNameOfTheChildWhenUpdatingItsNameIfTheChildAlreadyHasSomeName(): void - { - $ruleName1 = 'something'; - $ruleName2 = 'something else'; - - $rule = Stub::pass(1); - $rule->setName($ruleName1); - - $sut = new CompositeSub($rule); - $sut->setName($ruleName2); - - self::assertSame($ruleName1, $rule->getName()); - } - - #[Test] - public function itNotShouldUpdateTheNameOfTheChildWhenAddingItIfTheChildAlreadyHasSomeName(): void - { - $ruleName1 = 'something'; - $ruleName2 = 'something else'; - - $rule = Stub::pass(1); - $rule->setName($ruleName1); - - $sut = new CompositeSub(); - $sut->setName($ruleName2); - $sut->addRule($rule); - - self::assertSame($ruleName1, $rule->getName()); - } - - #[Test] - public function itShouldReturnItsChildren(): void - { - $child1 = Stub::pass(1); - $child2 = Stub::pass(1); - $child3 = Stub::pass(1); - - $sut = new CompositeSub($child1, $child2, $child3); - $children = $sut->getRules(); - - self::assertCount(3, $children); - self::assertSame($child1, $children[0]); - self::assertSame($child2, $children[1]); - self::assertSame($child3, $children[2]); - } - - #[Test] - public function itShouldAssertWithAllChildrenAndNotThrowAnExceptionWhenThereAreNoIssues(): void - { - $input = 'something'; - - $child1 = Stub::pass(1); - $child2 = Stub::pass(1); - $child3 = Stub::pass(1); - - $this->expectNotToPerformAssertions(); - - $sut = new CompositeSub($child1, $child2, $child3); - $sut->assert($input); - } - - #[Test] - public function itShouldAssertWithAllChildrenAndThrowAnExceptionWhenThereAreIssues(): void - { - $sut = new CompositeSub(Stub::fail(1), Stub::fail(1), Stub::fail(1)); - - try { - $sut->assert('something'); - } catch (CompositeStubException $exception) { - self::assertCount(3, $exception->getChildren()); - } - } - - #[Test] - public function itShouldUpdateTheTemplateOfEveryChildrenWhenAsserting(): void - { - $template = 'This is my template'; - - $sut = new CompositeSub( - Stub::fail(1), - Stub::fail(1), - Stub::fail(1) - ); - $sut->setTemplate($template); - - try { - $sut->assert('something'); - } catch (CompositeStubException $exception) { - foreach ($exception->getChildren() as $child) { - self::assertEquals($template, $child->getMessage()); - } - } - } - - #[Test] - public function itShouldUpdateTheTemplateOfEveryTheChildrenOfSomeChildWhenAsserting(): void - { - $template = 'This is my template'; - - $sut = new CompositeSub( - Stub::fail(1), - Stub::fail(1), - new CompositeSub(Stub::fail(1)) - ); - $sut->setTemplate($template); - - try { - $sut->assert('something'); - } catch (CompositeStubException $exception) { - foreach ($exception->getChildren() as $child) { - self::assertEquals($template, $child->getMessage()); - if (!$child instanceof CompositeStubException) { - continue; - } - - self::assertNotFalse(current($child->getChildren())); - self::assertEquals($template, current($child->getChildren())->getMessage()); - } - } - } -} diff --git a/tests/unit/Rules/AllOfTest.php b/tests/unit/Rules/AllOfTest.php index 8405d054c..e7a3f9663 100644 --- a/tests/unit/Rules/AllOfTest.php +++ b/tests/unit/Rules/AllOfTest.php @@ -21,14 +21,13 @@ final class AllOfTest extends RuleTestCase /** @return iterable */ public static function providerForValidInput(): iterable { - yield 'pass' => [new AllOf(Stub::pass(1)), []]; yield 'pass, pass' => [new AllOf(Stub::pass(1), Stub::pass(1)), []]; + yield 'pass, pass, pass' => [new AllOf(Stub::pass(1), Stub::pass(1), Stub::pass(1)), []]; } /** @return iterable */ public static function providerForInvalidInput(): iterable { - yield 'fail' => [new AllOf(Stub::fail(1)), []]; yield 'pass, fail' => [new AllOf(Stub::pass(1), Stub::fail(1)), []]; yield 'fail, pass' => [new AllOf(Stub::fail(1), Stub::pass(1)), []]; yield 'pass, pass, fail' => [new AllOf(Stub::pass(1), Stub::pass(1), Stub::fail(1)), []]; diff --git a/tests/unit/Rules/AnyOfTest.php b/tests/unit/Rules/AnyOfTest.php index 8e5b194f5..c29a14bc5 100644 --- a/tests/unit/Rules/AnyOfTest.php +++ b/tests/unit/Rules/AnyOfTest.php @@ -21,7 +21,6 @@ final class AnyOfTest extends RuleTestCase /** @return iterable */ public static function providerForValidInput(): iterable { - yield 'pass' => [new AnyOf(Stub::pass(1)), []]; yield 'fail, pass' => [new AnyOf(Stub::fail(1), Stub::pass(1)), []]; yield 'fail, fail, pass' => [new AnyOf(Stub::fail(1), Stub::fail(1), Stub::pass(1)), []]; yield 'fail, pass, fail' => [new AnyOf(Stub::fail(1), Stub::pass(1), Stub::fail(1)), []]; @@ -30,7 +29,6 @@ public static function providerForValidInput(): iterable /** @return iterable */ public static function providerForInvalidInput(): iterable { - yield 'fail' => [new AnyOf(Stub::fail(1)), []]; yield 'fail, fail' => [new AnyOf(Stub::fail(1), Stub::fail(1)), []]; yield 'fail, fail, fail' => [new AnyOf(Stub::fail(1), Stub::fail(1), Stub::fail(1)), []]; } diff --git a/tests/unit/Rules/CompositeTest.php b/tests/unit/Rules/CompositeTest.php new file mode 100644 index 000000000..20e4d4082 --- /dev/null +++ b/tests/unit/Rules/CompositeTest.php @@ -0,0 +1,84 @@ + + * SPDX-License-Identifier: MIT + */ + +declare(strict_types=1); + +namespace Respect\Validation\Rules; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; +use Respect\Validation\Test\Rules\ConcreteComposite; +use Respect\Validation\Test\Rules\Stub; +use Respect\Validation\Test\TestCase; + +#[Group('core')] +#[CoversClass(Composite::class)] +final class CompositeTest extends TestCase +{ + #[Test] + public function itShouldReturnItsChildren(): void + { + $expected = [Stub::daze(), Stub::daze(), Stub::daze()]; + $sut = new ConcreteComposite(...$expected); + $actual = $sut->getRules(); + + self::assertCount(3, $actual); + self::assertEquals($expected, $actual); + } + + #[Test] + public function itShouldDefineAndRetrieveTemplate(): void + { + $template = 'This is a template'; + + $sut = new ConcreteComposite(Stub::daze(), Stub::daze()); + $sut->setTemplate($template); + + self::assertEquals($template, $sut->getTemplate()); + } + + #[Test] + public function itShouldUpdateTheNameOfTheChildWhenUpdatingItsName(): void + { + $ruleName = 'something'; + + $rule1 = Stub::daze(); + $rule2 = Stub::daze(); + + $composite = new ConcreteComposite($rule1, $rule2); + + self::assertNull($rule1->getName()); + self::assertNull($rule2->getName()); + + $composite->setName($ruleName); + + self::assertEquals($ruleName, $rule1->getName()); + self::assertEquals($ruleName, $rule2->getName()); + self::assertEquals($ruleName, $composite->getName()); + } + + #[Test] + public function itShouldNotUpdateTheNameOfTheChildWhenUpdatingItsNameIfTheChildAlreadyHasSomeName(): void + { + $ruleName1 = 'something'; + $ruleName2 = 'something else'; + + $rule1 = Stub::daze(); + $rule1->setName($ruleName1); + + $rule2 = Stub::daze(); + $rule2->setName($ruleName1); + + $composite = new ConcreteComposite($rule1, $rule2); + $composite->setName($ruleName2); + + self::assertEquals($ruleName1, $rule1->getName()); + self::assertEquals($ruleName1, $rule2->getName()); + self::assertEquals($ruleName2, $composite->getName()); + } +} diff --git a/tests/unit/Rules/ContainsAnyTest.php b/tests/unit/Rules/ContainsAnyTest.php index 27e3685b8..b9e863fd9 100644 --- a/tests/unit/Rules/ContainsAnyTest.php +++ b/tests/unit/Rules/ContainsAnyTest.php @@ -11,12 +11,24 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\Test; +use Respect\Validation\Exceptions\InvalidRuleConstructorException; use Respect\Validation\Test\RuleTestCase; #[Group('rule')] #[CoversClass(ContainsAny::class)] final class ContainsAnyTest extends RuleTestCase { + #[Test] + public function itShouldThrowAnExceptionWhenThereAreNoNeedles(): void + { + $this->expectException(InvalidRuleConstructorException::class); + $this->expectExceptionMessage('At least one value must be provided'); + + // @phpstan-ignore-next-line + new ContainsAny([]); + } + /** @return iterable */ public static function providerForValidInput(): iterable { diff --git a/tests/unit/Rules/NoneOfTest.php b/tests/unit/Rules/NoneOfTest.php index 5dc1cab29..4c1597f14 100644 --- a/tests/unit/Rules/NoneOfTest.php +++ b/tests/unit/Rules/NoneOfTest.php @@ -21,7 +21,6 @@ final class NoneOfTest extends RuleTestCase /** @return iterable */ public static function providerForValidInput(): iterable { - yield 'fail' => [new NoneOf(Stub::fail(1)), []]; yield 'fail, fail' => [new NoneOf(Stub::fail(1), Stub::fail(1)), []]; yield 'fail, fail, fail' => [new NoneOf(Stub::fail(1), Stub::fail(1), Stub::fail(1)), []]; } @@ -29,7 +28,6 @@ public static function providerForValidInput(): iterable /** @return iterable */ public static function providerForInvalidInput(): iterable { - yield 'pass' => [new NoneOf(Stub::pass(1)), []]; yield 'pass, fail' => [new NoneOf(Stub::pass(1), Stub::fail(1)), []]; yield 'fail, pass' => [new NoneOf(Stub::fail(1), Stub::pass(1)), []]; yield 'pass, pass, fail' => [new NoneOf(Stub::pass(1), Stub::pass(1), Stub::fail(1)), []]; diff --git a/tests/unit/Rules/OneOfTest.php b/tests/unit/Rules/OneOfTest.php index fb64dfb96..5e1cb64e1 100644 --- a/tests/unit/Rules/OneOfTest.php +++ b/tests/unit/Rules/OneOfTest.php @@ -21,7 +21,6 @@ final class OneOfTest extends RuleTestCase /** @return iterable */ public static function providerForValidInput(): iterable { - yield 'pass' => [new OneOf(Stub::pass(1)), []]; yield 'fail, pass' => [new OneOf(Stub::fail(1), Stub::pass(1)), []]; yield 'pass, fail' => [new OneOf(Stub::pass(1), Stub::fail(1)), []]; yield 'pass, fail, fail' => [new OneOf(Stub::pass(1), Stub::fail(1), Stub::fail(1)), []]; @@ -32,7 +31,6 @@ public static function providerForValidInput(): iterable /** @return iterable */ public static function providerForInvalidInput(): iterable { - yield 'fail' => [new OneOf(Stub::fail(1)), []]; yield 'fail, fail' => [new OneOf(Stub::fail(1), Stub::fail(1)), []]; yield 'fail, fail, fail' => [new OneOf(Stub::fail(1), Stub::fail(1), Stub::fail(1)), []]; yield 'fail, pass, pass' => [new OneOf(Stub::fail(1), Stub::pass(1), Stub::pass(1)), []];