|
15 | 15 | use Respect\Validation\Rule; |
16 | 16 | use Respect\Validation\Rules\Core\Composite; |
17 | 17 |
|
| 18 | +use function array_filter; |
18 | 19 | use function array_map; |
19 | 20 | use function array_reduce; |
| 21 | +use function count; |
| 22 | +use function usort; |
20 | 23 |
|
21 | 24 | #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] |
22 | 25 | #[Template( |
23 | | - 'Only one of these rules must pass for {{name}}', |
24 | | - 'Only one of these rules must not pass for {{name}}', |
| 26 | + '{{name}} must pass one of the rules', |
| 27 | + '{{name}} must pass one of the rules', |
| 28 | + self::TEMPLATE_NONE, |
| 29 | +)] |
| 30 | +#[Template( |
| 31 | + '{{name}} must pass only one of the rules', |
| 32 | + '{{name}} must pass only one of the rules', |
| 33 | + self::TEMPLATE_MORE_THAN_ONE, |
25 | 34 | )] |
26 | 35 | final class OneOf extends Composite |
27 | 36 | { |
| 37 | + public const TEMPLATE_NONE = '__none__'; |
| 38 | + public const TEMPLATE_MORE_THAN_ONE = '__more_than_one__'; |
| 39 | + |
28 | 40 | public function evaluate(mixed $input): Result |
29 | 41 | { |
30 | 42 | $children = array_map(static fn (Rule $rule) => $rule->evaluate($input), $this->rules); |
31 | 43 | $valid = array_reduce($children, static fn (bool $carry, Result $result) => $carry xor $result->isValid, false); |
| 44 | + $validChildren = array_filter($children, static fn (Result $result): bool => $result->isValid); |
| 45 | + $template = self::TEMPLATE_NONE; |
| 46 | + if (count($validChildren) > 1) { |
| 47 | + // Put the failed children at the top, so it makes sense in the main error message |
| 48 | + usort($children, static fn (Result $a, Result $b): int => $a->isValid <=> $b->isValid); |
| 49 | + |
| 50 | + $template = self::TEMPLATE_MORE_THAN_ONE; |
| 51 | + $children = array_map( |
| 52 | + static fn (Result $child) => $child->isValid ? $child->withInvertedValidation() : $child, |
| 53 | + $children |
| 54 | + ); |
| 55 | + } |
32 | 56 |
|
33 | | - return (new Result($valid, $input, $this))->withChildren(...$children); |
| 57 | + return (new Result($valid, $input, $this, [], $template))->withChildren(...$children); |
34 | 58 | } |
35 | 59 | } |
0 commit comments