Skip to content

Commit

Permalink
Only create Length with subsequents when possible
Browse files Browse the repository at this point in the history
Since I updated the validation engine[1], it became possible to create
results with subsequents[2]. This commit changes the "Length", allowing
it to create a result with a subsequent only when it's possible. That
will improve the clarity of the error messages.

[1]: 238f2d5
[2]: 52e628f
  • Loading branch information
henriquemoody committed Dec 16, 2024
1 parent 6e3ed79 commit aa293de
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 19 deletions.
21 changes: 21 additions & 0 deletions docs/rules/Length.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
51 changes: 39 additions & 12 deletions library/Rules/Length.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<mixed>|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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
));
15 changes: 15 additions & 0 deletions tests/feature/Rules/LengthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]
));
12 changes: 6 additions & 6 deletions tests/feature/TranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
));

Expand Down

0 comments on commit aa293de

Please sign in to comment.