diff --git a/docs/rules/Length.md b/docs/rules/Length.md index 08219be81..c64387255 100644 --- a/docs/rules/Length.md +++ b/docs/rules/Length.md @@ -24,11 +24,32 @@ v::length(v::equals(0))->isValid(new SplPriorityQueue()); // true ### `Length::TEMPLATE_STANDARD` +Used when it's possible to get the length of the input. + | Mode | Template | |------------|---------------| | `default` | The length of | | `inverted` | The length of | +This template serve as message prefixes.: + +```php +v::length(v::equals(3))->assert('tulip'); +// Message: The length of "tulip" must be equal to 3 + +v::not(v::length(v::equals(4)))->assert('rose'); +// Message: The length of "rose" must not be equal to 4 +``` + +### `Length::TEMPLATE_WRONG_TYPE` + +Used when it's impossible to get the length of the input. + +| Mode | Template | +|------------|----------------------------------------------------| +| `default` | {{name}} must be a countable value or a string | +| `inverted` | {{name}} must not be a countable value or a string | + ## Template placeholders | Placeholder | Description | diff --git a/library/Rules/Length.php b/library/Rules/Length.php index a61704d44..b6f799bfe 100644 --- a/library/Rules/Length.php +++ b/library/Rules/Length.php @@ -13,39 +13,66 @@ use Countable as PhpCountable; use Respect\Validation\Message\Template; use Respect\Validation\Result; -use Respect\Validation\Rules\Core\Binder; use Respect\Validation\Rules\Core\Wrapper; +use function array_map; use function count; +use function is_array; use function is_string; use function mb_strlen; use function ucfirst; #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] -#[Template('The length of', 'The length of')] +#[Template( + 'The length of', + 'The length of', + self::TEMPLATE_STANDARD +)] +#[Template( + '{{name}} must be a countable value or a string', + '{{name}} must not be a countable value or a string', + self::TEMPLATE_WRONG_TYPE +)] final class Length extends Wrapper { + public const TEMPLATE_WRONG_TYPE = '__wrong_type__'; + public function evaluate(mixed $input): Result { - $typeResult = (new Binder($this, new OneOf(new StringType(), new Countable())))->evaluate($input); - if (!$typeResult->isValid) { - $result = $this->rule->evaluate($input); - - return Result::failed($input, $this)->withSubsequent($result)->withId('length' . ucfirst($result->id)); + $length = $this->extractLength($input); + if ($length === null) { + return Result::failed($input, $this, [], self::TEMPLATE_WRONG_TYPE) + ->withId('length' . ucfirst($this->rule->evaluate($input)->id)); } - $result = $this->rule->evaluate($this->extractLength($input))->withInput($input)->withPrefixedId('length'); + return $this->enrichResult($input, $this->rule->evaluate($length)); + } + + private function enrichResult(mixed $input, Result $result): Result + { + if (!$result->allowsSubsequent()) { + return $result + ->withInput($input) + ->withChildren( + ...array_map(fn(Result $child) => $this->enrichResult($input, $child), $result->children) + ); + } - return (new Result($result->isValid, $input, $this, id: $result->id))->withSubsequent($result); + return (new Result($result->isValid, $input, $this, id: $result->id)) + ->withPrefixedId('length') + ->withSubsequent($result->withInput($input)); } - /** @param array|PhpCountable|string $input */ - private function extractLength(array|PhpCountable|string $input): int + private function extractLength(mixed $input): ?int { if (is_string($input)) { return (int) mb_strlen($input); } - return count($input); + if ($input instanceof PhpCountable || is_array($input)) { + return count($input); + } + + return null; } } diff --git a/tests/feature/GetFullMessageShouldIncludeAllValidationMessagesInAChainTest.php b/tests/feature/GetFullMessageShouldIncludeAllValidationMessagesInAChainTest.php index 67f447096..aa50a376f 100644 --- a/tests/feature/GetFullMessageShouldIncludeAllValidationMessagesInAChainTest.php +++ b/tests/feature/GetFullMessageShouldIncludeAllValidationMessagesInAChainTest.php @@ -14,6 +14,6 @@ <<<'FULL_MESSAGE' - All of the required rules must pass for 0 - 0 must be a string - - The length of 0 must be between 2 and 15 + - 0 must be a countable value or a string FULL_MESSAGE, )); diff --git a/tests/feature/Rules/LengthTest.php b/tests/feature/Rules/LengthTest.php index 6c2f981b9..52a08f79e 100644 --- a/tests/feature/Rules/LengthTest.php +++ b/tests/feature/Rules/LengthTest.php @@ -41,3 +41,18 @@ '- The length of Cactus must be equal to 3', ['lengthEquals' => 'The length of Cactus must be equal to 3'] )); + +test('Chained wrapped rule', expectAll( + fn() => v::length(v::between(5, 7)->odd())->assert([]), + 'The length of `[]` must be between 5 and 7', + <<<'FULL_MESSAGE' + - All of the required rules must pass for `[]` + - The length of `[]` must be between 5 and 7 + - The length of `[]` must be an odd number + FULL_MESSAGE, + [ + '__root__' => 'All of the required rules must pass for `[]`', + 'lengthBetween' => 'The length of `[]` must be between 5 and 7', + 'lengthOdd' => 'The length of `[]` must be an odd number', + ] +)); diff --git a/tests/feature/TranslatorTest.php b/tests/feature/TranslatorTest.php index 1af02d39b..9870065ae 100644 --- a/tests/feature/TranslatorTest.php +++ b/tests/feature/TranslatorTest.php @@ -16,20 +16,20 @@ function (): void { ValidatorDefaults::setTranslator(new ArrayTranslator([ 'All of the required rules must pass for {{name}}' => 'Todas as regras requeridas devem passar para {{name}}', 'The length of' => 'O comprimento de', - '{{name}} must be of type string' => '{{name}} deve ser do tipo string', + '{{name}} must be a string' => '{{name}} deve ser uma string', '{{name}} must be between {{minValue}} and {{maxValue}}' => '{{name}} deve possuir de {{minValue}} a {{maxValue}} caracteres', '{{name}} must be a valid telephone number for country {{countryName|trans}}' => '{{name}} deve ser um número de telefone válido para o país {{countryName|trans}}', 'United States' => 'Estados Unidos', ])); - Validator::stringType()->lengthBetween(2, 15)->phone('US')->assert(0); + Validator::stringType()->lengthBetween(2, 15)->phone('US')->assert([]); }, <<<'FULL_MESSAGE' - - Todas as regras requeridas devem passar para 0 - - 0 must be a string - - O comprimento de 0 deve possuir de 2 a 15 caracteres - - 0 deve ser um número de telefone válido para o país Estados Unidos + - Todas as regras requeridas devem passar para `[]` + - `[]` deve ser uma string + - O comprimento de `[]` deve possuir de 2 a 15 caracteres + - `[]` deve ser um número de telefone válido para o país Estados Unidos FULL_MESSAGE, ));