Recreate "Max" rule

The "Max" rule is not a transformation, validating the maximum value in
the input against a given rule.

Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
Henrique Moody 2024-02-29 22:31:15 +01:00
parent 81befe8fa1
commit cea77d2a46
No known key found for this signature in database
GPG key ID: 221E9281655813A6
15 changed files with 279 additions and 0 deletions

View file

@ -51,6 +51,7 @@
- [In](rules/In.md)
- [LessThan](rules/LessThan.md)
- [LessThanOrEqual](rules/LessThanOrEqual.md)
- [Max](rules/Max.md)
- [Min](rules/Min.md)
## Composite
@ -249,6 +250,7 @@
- [Call](rules/Call.md)
- [Each](rules/Each.md)
- [Max](rules/Max.md)
- [Min](rules/Min.md)
## Types
@ -359,6 +361,7 @@
- [Lowercase](rules/Lowercase.md)
- [Luhn](rules/Luhn.md)
- [MacAddress](rules/MacAddress.md)
- [Max](rules/Max.md)
- [MaxAge](rules/MaxAge.md)
- [Mimetype](rules/Mimetype.md)
- [Min](rules/Min.md)

View file

@ -36,4 +36,5 @@ See also:
- [Length](Length.md)
- [LessThan](LessThan.md)
- [LessThanOrEqual](LessThanOrEqual.md)
- [Max](Max.md)
- [Min](Min.md)

View file

@ -30,4 +30,5 @@ See also:
- [Between](Between.md)
- [GreaterThanOrEqual](GreaterThanOrEqual.md)
- [LessThanOrEqual](LessThanOrEqual.md)
- [Max](Max.md)
- [Min](Min.md)

View file

@ -36,6 +36,7 @@ See also:
- [Length](Length.md)
- [LessThan](LessThan.md)
- [LessThanOrEqual](LessThanOrEqual.md)
- [Max](Max.md)
- [MaxAge](MaxAge.md)
- [Min](Min.md)
- [MinAge](MinAge.md)

View file

@ -33,3 +33,4 @@ See also:
- [Each](Each.md)
- [Instance](Instance.md)
- [IterableVal](IterableVal.md)
- [Max](Max.md)

View file

@ -30,4 +30,5 @@ See also:
- [Between](Between.md)
- [GreaterThanOrEqual](GreaterThanOrEqual.md)
- [LessThanOrEqual](LessThanOrEqual.md)
- [Max](Max.md)
- [Min](Min.md)

View file

@ -35,6 +35,7 @@ See also:
- [GreaterThan](GreaterThan.md)
- [GreaterThanOrEqual](GreaterThanOrEqual.md)
- [LessThan](LessThan.md)
- [Max](Max.md)
- [MaxAge](MaxAge.md)
- [Min](Min.md)
- [MinAge](MinAge.md)

47
docs/rules/Max.md Normal file
View file

@ -0,0 +1,47 @@
# Max
- `Max(Validatable $rule)`
Validates the maximum value of the input against a given rule.
```php
v::max(v::equals(30))->validate([10, 20, 30]); // true
v::max(v::between('e', 'g'))->validate(['b', 'd', 'f']); // true
v::max(v::greaterThan(new DateTime('today')))
->validate([new DateTime('yesterday'), new DateTime('tomorrow')]); // true
v::max(v::greaterThan(15))->validate([4, 8, 12]); // false
```
## Note
This rule uses [IterableType](IterableType.md) and [NotEmpty](NotEmpty.md) internally. If an input is non-iterable or
empty, the validation will fail.
## Categorization
- Comparisons
- Transformations
## Changelog
| Version | Description |
|--------:|-----------------------------|
| 3.0.0 | Became a transformation |
| 2.0.0 | Became always inclusive |
| 1.0.0 | Became inclusive by default |
| 0.3.9 | Created |
***
See also:
- [Between](Between.md)
- [GreaterThan](GreaterThan.md)
- [GreaterThanOrEqual](GreaterThanOrEqual.md)
- [IterableType](IterableType.md)
- [LessThan](LessThan.md)
- [LessThanOrEqual](LessThanOrEqual.md)
- [Min](Min.md)
- [NotEmpty](NotEmpty.md)

View file

@ -43,4 +43,5 @@ See also:
- [GreaterThanOrEqual](GreaterThanOrEqual.md)
- [LessThan](LessThan.md)
- [LessThanOrEqual](LessThanOrEqual.md)
- [Max](Max.md)
- [NotEmpty](NotEmpty.md)

View file

@ -49,6 +49,7 @@ Version | Description
See also:
- [Each](Each.md)
- [Max](Max.md)
- [Min](Min.md)
- [NoWhitespace](NoWhitespace.md)
- [NotBlank](NotBlank.md)

View file

@ -198,6 +198,8 @@ interface ChainedValidator extends Validatable
public function lessThanOrEqual(mixed $compareTo): ChainedValidator;
public function max(Validatable $rule): ChainedValidator;
public function maxAge(int $age, ?string $format = null): ChainedValidator;
public function min(Validatable $rule): ChainedValidator;

32
library/Rules/Max.php Normal file
View file

@ -0,0 +1,32 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\FilteredNonEmptyArray;
use function max;
#[Template('As the maximum of {{name}},', 'As the maximum of {{name}},')]
#[Template('The maximum of', 'The maximum of', self::TEMPLATE_NAMED)]
final class Max extends FilteredNonEmptyArray
{
public const TEMPLATE_NAMED = '__named__';
/** @param non-empty-array<mixed> $input */
protected function evaluateNonEmptyArray(array $input): Result
{
$result = $this->rule->evaluate(max($input));
$template = $this->getName() === null ? self::TEMPLATE_STANDARD : self::TEMPLATE_NAMED;
return (new Result($result->isValid, $input, $this, [], $template))->withNextSibling($result);
}
}

View file

@ -200,6 +200,8 @@ interface StaticValidator
public static function lessThanOrEqual(mixed $compareTo): ChainedValidator;
public static function max(Validatable $rule): ChainedValidator;
public static function maxAge(int $age, ?string $format = null): ChainedValidator;
public static function min(Validatable $rule): ChainedValidator;

View file

@ -0,0 +1,100 @@
--FILE--
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use Respect\Validation\Validator as v;
$empty = [];
$nonIterable = null;
$default = [1, 2, 3];
$negative = [-3, -2, -1];
run([
// Simple
'Non-iterable' => [v::max(v::negative()), $nonIterable],
'Empty' => [v::max(v::negative()), $empty],
'Default' => [v::max(v::negative()), $default],
'Negative' => [v::not(v::max(v::negative())), $negative],
'With wrapped name, default' => [v::max(v::negative()->setName('Wrapped'))->setName('Wrapper'), $default],
'With wrapper name, default' => [v::max(v::negative())->setName('Wrapper'), $default],
'With wrapped name, negative' => [v::not(v::max(v::negative()->setName('Wrapped')))->setName('Wrapper'), $negative],
'With wrapper name, negative' => [v::not(v::max(v::negative()))->setName('Wrapper'), $negative],
'With template, default' => [v::max(v::negative()), $default, 'The maximum of the value is not what we expect'],
]);
?>
--EXPECT--
Non-iterable
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
`null` must be of type iterable
- `null` must be of type iterable
[
'max' => '`null` must be of type iterable',
]
Empty
⎺⎺⎺⎺⎺
The value must not be empty
- The value must not be empty
[
'max' => 'The value must not be empty',
]
Default
⎺⎺⎺⎺⎺⎺⎺
As the maximum of `[1, 2, 3]`, 3 must be negative
- As the maximum of `[1, 2, 3]`, 3 must be negative
[
'max' => 'As the maximum of `[1, 2, 3]`, 3 must be negative',
]
Negative
⎺⎺⎺⎺⎺⎺⎺⎺
As the maximum of `[-3, -2, -1]`, -1 must not be negative
- As the maximum of `[-3, -2, -1]`, -1 must not be negative
[
'max' => 'As the maximum of `[-3, -2, -1]`, -1 must not be negative',
]
With wrapped name, default
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
The maximum of Wrapped must be negative
- The maximum of Wrapped must be negative
[
'Wrapped' => 'The maximum of Wrapped must be negative',
]
With wrapper name, default
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
The maximum of Wrapper must be negative
- The maximum of Wrapper must be negative
[
'Wrapper' => 'The maximum of Wrapper must be negative',
]
With wrapped name, negative
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
The maximum of Wrapped must not be negative
- The maximum of Wrapped must not be negative
[
'Wrapped' => 'The maximum of Wrapped must not be negative',
]
With wrapper name, negative
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
The maximum of Wrapper must not be negative
- The maximum of Wrapper must not be negative
[
'Wrapper' => 'The maximum of Wrapper must not be negative',
]
With template, default
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
The maximum of the value is not what we expect
- The maximum of the value is not what we expect
[
'max' => 'The maximum of the value is not what we expect',
]

View file

@ -0,0 +1,85 @@
<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Test\Rules\Stub;
use Respect\Validation\Test\TestCase;
#[Group('rule')]
#[CoversClass(Max::class)]
final class MaxTest extends TestCase
{
#[Test]
#[DataProvider('providerForNonIterableValues')]
public function itShouldInvalidateNonIterableValues(mixed $input): void
{
$rule = new Max(Stub::daze());
self::assertInvalidInput($rule, $input);
}
/** @param iterable<mixed> $input */
#[Test]
#[DataProvider('providerForEmptyIterableValues')]
public function itShouldInvalidateEmptyIterableValues(iterable $input): void
{
$rule = new Max(Stub::daze());
self::assertInvalidInput($rule, $input);
}
/** @param iterable<mixed> $input */
#[Test]
#[DataProvider('providerForNonEmptyIterableValues')]
public function itShouldValidateNonEmptyIterableValuesWhenWrappedRulePasses(iterable $input): void
{
$rule = new Max(Stub::pass(1));
self::assertValidInput($rule, $input);
}
/** @param iterable<mixed> $input */
#[Test]
#[DataProvider('providerForMaxValues')]
public function itShouldValidateWithTheMaximumValue(iterable $input, mixed $min): void
{
$wrapped = Stub::pass(1);
$rule = new Max($wrapped);
$rule->evaluate($input);
self::assertSame($min, $wrapped->inputs[0]);
}
/** @return array<string, array{iterable<mixed>, mixed}> */
public static function providerForMaxValues(): array
{
$yesterday = new DateTimeImmutable('yesterday');
$today = new DateTimeImmutable('today');
$tomorrow = new DateTimeImmutable('tomorrow');
return [
'3 DateTime objects' => [[$yesterday, $today, $tomorrow], $tomorrow],
'2 DateTime objects' => [[$yesterday, $today], $today],
'1 DateTime objects' => [[$yesterday], $yesterday],
'3 integers' => [[1, 2, 3], 3],
'2 integers' => [[1, 2], 2],
'1 integer' => [[1], 1],
'3 characters' => [['a', 'b', 'c'], 'c'],
'2 characters' => [['a', 'b'], 'b'],
'1 character' => [['a'], 'a'],
];
}
}