Skip to content

Commit aa293de

Browse files
committed
Only create Length with subsequents when possible
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
1 parent 6e3ed79 commit aa293de

File tree

5 files changed

+82
-19
lines changed

5 files changed

+82
-19
lines changed

docs/rules/Length.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,32 @@ v::length(v::equals(0))->isValid(new SplPriorityQueue()); // true
2424

2525
### `Length::TEMPLATE_STANDARD`
2626

27+
Used when it's possible to get the length of the input.
28+
2729
| Mode | Template |
2830
|------------|---------------|
2931
| `default` | The length of |
3032
| `inverted` | The length of |
3133

34+
This template serve as message prefixes.:
35+
36+
```php
37+
v::length(v::equals(3))->assert('tulip');
38+
// Message: The length of "tulip" must be equal to 3
39+
40+
v::not(v::length(v::equals(4)))->assert('rose');
41+
// Message: The length of "rose" must not be equal to 4
42+
```
43+
44+
### `Length::TEMPLATE_WRONG_TYPE`
45+
46+
Used when it's impossible to get the length of the input.
47+
48+
| Mode | Template |
49+
|------------|----------------------------------------------------|
50+
| `default` | {{name}} must be a countable value or a string |
51+
| `inverted` | {{name}} must not be a countable value or a string |
52+
3253
## Template placeholders
3354

3455
| Placeholder | Description |

library/Rules/Length.php

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,66 @@
1313
use Countable as PhpCountable;
1414
use Respect\Validation\Message\Template;
1515
use Respect\Validation\Result;
16-
use Respect\Validation\Rules\Core\Binder;
1716
use Respect\Validation\Rules\Core\Wrapper;
1817

18+
use function array_map;
1919
use function count;
20+
use function is_array;
2021
use function is_string;
2122
use function mb_strlen;
2223
use function ucfirst;
2324

2425
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
25-
#[Template('The length of', 'The length of')]
26+
#[Template(
27+
'The length of',
28+
'The length of',
29+
self::TEMPLATE_STANDARD
30+
)]
31+
#[Template(
32+
'{{name}} must be a countable value or a string',
33+
'{{name}} must not be a countable value or a string',
34+
self::TEMPLATE_WRONG_TYPE
35+
)]
2636
final class Length extends Wrapper
2737
{
38+
public const TEMPLATE_WRONG_TYPE = '__wrong_type__';
39+
2840
public function evaluate(mixed $input): Result
2941
{
30-
$typeResult = (new Binder($this, new OneOf(new StringType(), new Countable())))->evaluate($input);
31-
if (!$typeResult->isValid) {
32-
$result = $this->rule->evaluate($input);
33-
34-
return Result::failed($input, $this)->withSubsequent($result)->withId('length' . ucfirst($result->id));
42+
$length = $this->extractLength($input);
43+
if ($length === null) {
44+
return Result::failed($input, $this, [], self::TEMPLATE_WRONG_TYPE)
45+
->withId('length' . ucfirst($this->rule->evaluate($input)->id));
3546
}
3647

37-
$result = $this->rule->evaluate($this->extractLength($input))->withInput($input)->withPrefixedId('length');
48+
return $this->enrichResult($input, $this->rule->evaluate($length));
49+
}
50+
51+
private function enrichResult(mixed $input, Result $result): Result
52+
{
53+
if (!$result->allowsSubsequent()) {
54+
return $result
55+
->withInput($input)
56+
->withChildren(
57+
...array_map(fn(Result $child) => $this->enrichResult($input, $child), $result->children)
58+
);
59+
}
3860

39-
return (new Result($result->isValid, $input, $this, id: $result->id))->withSubsequent($result);
61+
return (new Result($result->isValid, $input, $this, id: $result->id))
62+
->withPrefixedId('length')
63+
->withSubsequent($result->withInput($input));
4064
}
4165

42-
/** @param array<mixed>|PhpCountable|string $input */
43-
private function extractLength(array|PhpCountable|string $input): int
66+
private function extractLength(mixed $input): ?int
4467
{
4568
if (is_string($input)) {
4669
return (int) mb_strlen($input);
4770
}
4871

49-
return count($input);
72+
if ($input instanceof PhpCountable || is_array($input)) {
73+
return count($input);
74+
}
75+
76+
return null;
5077
}
5178
}

tests/feature/GetFullMessageShouldIncludeAllValidationMessagesInAChainTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
<<<'FULL_MESSAGE'
1515
- All of the required rules must pass for 0
1616
- 0 must be a string
17-
- The length of 0 must be between 2 and 15
17+
- 0 must be a countable value or a string
1818
FULL_MESSAGE,
1919
));

tests/feature/Rules/LengthTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,18 @@
4141
'- The length of Cactus must be equal to 3',
4242
['lengthEquals' => 'The length of Cactus must be equal to 3']
4343
));
44+
45+
test('Chained wrapped rule', expectAll(
46+
fn() => v::length(v::between(5, 7)->odd())->assert([]),
47+
'The length of `[]` must be between 5 and 7',
48+
<<<'FULL_MESSAGE'
49+
- All of the required rules must pass for `[]`
50+
- The length of `[]` must be between 5 and 7
51+
- The length of `[]` must be an odd number
52+
FULL_MESSAGE,
53+
[
54+
'__root__' => 'All of the required rules must pass for `[]`',
55+
'lengthBetween' => 'The length of `[]` must be between 5 and 7',
56+
'lengthOdd' => 'The length of `[]` must be an odd number',
57+
]
58+
));

tests/feature/TranslatorTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@ function (): void {
1616
ValidatorDefaults::setTranslator(new ArrayTranslator([
1717
'All of the required rules must pass for {{name}}' => 'Todas as regras requeridas devem passar para {{name}}',
1818
'The length of' => 'O comprimento de',
19-
'{{name}} must be of type string' => '{{name}} deve ser do tipo string',
19+
'{{name}} must be a string' => '{{name}} deve ser uma string',
2020
'{{name}} must be between {{minValue}} and {{maxValue}}' => '{{name}} deve possuir de {{minValue}} a {{maxValue}} caracteres',
2121
'{{name}} must be a valid telephone number for country {{countryName|trans}}'
2222
=> '{{name}} deve ser um número de telefone válido para o país {{countryName|trans}}',
2323
'United States' => 'Estados Unidos',
2424
]));
2525

26-
Validator::stringType()->lengthBetween(2, 15)->phone('US')->assert(0);
26+
Validator::stringType()->lengthBetween(2, 15)->phone('US')->assert([]);
2727
},
2828
<<<'FULL_MESSAGE'
29-
- Todas as regras requeridas devem passar para 0
30-
- 0 must be a string
31-
- O comprimento de 0 deve possuir de 2 a 15 caracteres
32-
- 0 deve ser um número de telefone válido para o país Estados Unidos
29+
- Todas as regras requeridas devem passar para `[]`
30+
- `[]` deve ser uma string
31+
- O comprimento de `[]` deve possuir de 2 a 15 caracteres
32+
- `[]` deve ser um número de telefone válido para o país Estados Unidos
3333
FULL_MESSAGE,
3434
));
3535

0 commit comments

Comments
 (0)