From 399be9826c97a28a42344dc911312eafe0b22e14 Mon Sep 17 00:00:00 2001 From: Henrique Moody Date: Tue, 17 Dec 2024 01:30:52 +0100 Subject: [PATCH] WIP: Improve messages in an array format - Name: That's what is in the placeholder, and could be used for path and id as well - Id: Tha could be the name of the rule, just a means to identify it and be able to build templates for it on that level. - Path: That's the real path of the rule, which will be useful when dealing with arrays or objects. --- library/Message/StandardFormatter.php | 78 +++++++++++++++++++------- library/Result.php | 18 +++--- library/Rules/DateTimeDiff.php | 2 +- library/Rules/Each.php | 6 +- library/Rules/Key.php | 2 +- library/Rules/KeyOptional.php | 2 +- library/Rules/Length.php | 2 +- library/Rules/Max.php | 5 +- library/Rules/Min.php | 2 +- library/Rules/Not.php | 2 +- library/Rules/NullOr.php | 2 +- library/Rules/Property.php | 2 +- library/Rules/PropertyOptional.php | 2 +- library/Rules/Size.php | 2 +- library/Rules/UndefOr.php | 2 +- tests/feature/Issues/Issue1033Test.php | 6 +- tests/feature/Issues/Issue1289Test.php | 2 +- tests/feature/Issues/Issue1334Test.php | 24 +++++--- tests/feature/Issues/Issue1348Test.php | 58 ++++++++++--------- tests/feature/Issues/Issue1469Test.php | 25 ++++++--- tests/feature/Issues/Issue796Test.php | 8 ++- tests/feature/Rules/AttributesTest.php | 6 +- tests/feature/Rules/EachTest.php | 78 +++++++++++++------------- tests/feature/Rules/PropertyTest.php | 17 ++++++ tests/unit/Rules/NotTest.php | 2 +- 25 files changed, 219 insertions(+), 136 deletions(-) diff --git a/library/Message/StandardFormatter.php b/library/Message/StandardFormatter.php index d3d9a6b7d..64fbdb7c3 100644 --- a/library/Message/StandardFormatter.php +++ b/library/Message/StandardFormatter.php @@ -14,12 +14,14 @@ use function array_filter; use function array_key_exists; +use function array_merge; use function array_reduce; use function array_values; use function count; use function current; use function is_array; use function is_string; +use function key; use function Respect\Stringifier\stringify; use function rtrim; use function sprintf; @@ -97,26 +99,45 @@ public function full( */ public function array(Result $result, array $templates, Translator $translator): array { + $whoamid = sprintf('%s.%s.%s', $result->rule::class, $result->id, $result->name); $selectedTemplates = $this->selectTemplates($result, $templates); $deduplicatedChildren = $this->extractDeduplicatedChildren($result); if (count($deduplicatedChildren) === 0 || $this->isFinalTemplate($result, $selectedTemplates)) { return [ - $result->id => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator), + $result->path ?? $result->id => $this->renderer->render( + $this->getTemplated($result, $selectedTemplates), + $translator + ), ]; } $messages = []; foreach ($deduplicatedChildren as $child) { - $messages[$child->id] = $this->array( + $childKey = $child->path ?? $child->id; + + $messages[$childKey] = $this->array( $child, $this->selectTemplates($child, $selectedTemplates), $translator ); - if (count($messages[$child->id]) !== 1) { + + if ($childKey == 'each' && is_array($messages['each'])) { + $messages = array_merge($messages, $messages['each']); + unset($messages['each']); + continue; + } + + if (count($messages[$childKey]) !== 1) { continue; } - $messages[$child->id] = current($messages[$child->id]); + $grantChildKey = key($messages[$childKey]); + if ($grantChildKey != $childKey) { + continue; + } + + + $messages[$grantChildKey] = current($messages[$grantChildKey]); } if (count($messages) > 1) { @@ -172,21 +193,27 @@ private function getTemplated(Result $result, array $templates): Result return $result; } - if (!isset($templates[$result->id]) && isset($templates['__root__'])) { - return $result->withTemplate($templates['__root__']); + $keys = [$result->name, $result->path, $result->id]; + foreach ($keys as $key) { + if (isset($templates[$key]) && is_string($templates[$key])) { + return $result->withTemplate($templates[$key]); + } } - if (!isset($templates[$result->id])) { - return $result; + if (isset($templates['__root__'])) { + return $result->withTemplate($templates['__root__']); } - $template = $templates[$result->id]; - if (is_string($template)) { - return $result->withTemplate($template); + if (!isset($templates[$result->id]) && !isset($templates[$result->path]) && !isset($templates[$result->name])) { + return $result; } throw new ComponentException( - sprintf('Template for "%s" must be a string, %s given', $result->id, stringify($template)) + sprintf( + 'Template for "%s" must be a string, %s given', + $result->path ?? $result->name ?? $result->id, + stringify($templates) + ) ); } @@ -195,15 +222,24 @@ private function getTemplated(Result $result, array $templates): Result */ private function isFinalTemplate(Result $result, array $templates): bool { - if (isset($templates[$result->id]) && is_string($templates[$result->id])) { - return true; + $keys = [$result->name, $result->path, $result->id]; + foreach ($keys as $key) { + if (isset($templates[$key]) && is_string($templates[$key])) { + return true; + } } if (count($templates) !== 1) { return false; } - return isset($templates['__root__']) || isset($templates[$result->id]); + foreach ($keys as $key) { + if (isset($templates[$key])) { + return true; + } + } + + return isset($templates['__root__']); } /** @@ -211,10 +247,12 @@ private function isFinalTemplate(Result $result, array $templates): bool * * @return array */ - private function selectTemplates(Result $message, array $templates): array + private function selectTemplates(Result $result, array $templates): array { - if (isset($templates[$message->id]) && is_array($templates[$message->id])) { - return $templates[$message->id]; + foreach ([$result->name, $result->path, $result->id] as $key) { + if (isset($templates[$key]) && is_array($templates[$key])) { + return $templates[$key]; + } } return $templates; @@ -227,7 +265,7 @@ private function extractDeduplicatedChildren(Result $result): array $deduplicatedResults = []; $duplicateCounters = []; foreach ($result->children as $child) { - $id = $child->id; + $id = $child->path ?? $child->id; if (isset($duplicateCounters[$id])) { $id .= '.' . ++$duplicateCounters[$id]; } elseif (array_key_exists($id, $deduplicatedResults)) { @@ -236,7 +274,7 @@ private function extractDeduplicatedChildren(Result $result): array $duplicateCounters[$id] = 2; $id .= '.2'; } - $deduplicatedResults[$id] = $child->isValid ? null : $child->withId($id); + $deduplicatedResults[$id] = $child->isValid ? null : $child->withId((string) $id); } return array_values(array_filter($deduplicatedResults)); diff --git a/library/Result.php b/library/Result.php index d308dab3c..36b29cf02 100644 --- a/library/Result.php +++ b/library/Result.php @@ -39,8 +39,8 @@ public function __construct( public readonly Mode $mode = Mode::DEFAULT, ?string $name = null, ?string $id = null, + public readonly string|int|null $path = null, public readonly ?Result $subsequent = null, - public readonly bool $unchangeableId = false, Result ...$children, ) { $this->name = $rule->getName() ?? $name; @@ -76,21 +76,17 @@ public function withTemplate(string $template): self public function withId(string $id): self { - if ($this->unchangeableId) { - return $this; - } - return $this->clone(id: $id); } - public function withUnchangeableId(string $id): self + public function withPath(string|int $path): self { - return $this->clone(id: $id, unchangeableId: true); + return $this->clone(path: $path); } - public function withPrefixedId(string $prefix): self + public function withPrefix(string $prefix): self { - if ($this->id === $this->name || $this->unchangeableId) { + if ($this->id === $this->name || $this->path !== null) { return $this; } @@ -176,8 +172,8 @@ private function clone( ?Mode $mode = null, ?string $name = null, ?string $id = null, + string|int|null $path = null, ?Result $subsequent = null, - ?bool $unchangeableId = null, ?array $children = null ): self { return new self( @@ -189,8 +185,8 @@ private function clone( $mode ?? $this->mode, $name ?? $this->name, $id ?? $this->id, + $path ?? $this->path, $subsequent ?? $this->subsequent, - $unchangeableId ?? $this->unchangeableId, ...($children ?? $this->children) ); } diff --git a/library/Rules/DateTimeDiff.php b/library/Rules/DateTimeDiff.php index 8f92116c0..994767047 100644 --- a/library/Rules/DateTimeDiff.php +++ b/library/Rules/DateTimeDiff.php @@ -105,7 +105,7 @@ private function enrichResult(string $now, mixed $input, Result $result): Result $template = $now === 'now' ? self::TEMPLATE_STANDARD : self::TEMPLATE_CUSTOMIZED; return (new Result($result->isValid, $input, $this, $parameters, $template, id: $result->id)) - ->withPrefixedId('dateTimeDiff') + ->withPrefix('dateTimeDiff') ->withSubsequent($result->withNameIfMissing($name)); } diff --git a/library/Rules/Each.php b/library/Rules/Each.php index 19397c0f0..acc2bb372 100644 --- a/library/Rules/Each.php +++ b/library/Rules/Each.php @@ -14,7 +14,6 @@ use Respect\Validation\Result; use Respect\Validation\Rules\Core\FilteredNonEmptyArray; -use function array_map; use function array_reduce; #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] @@ -27,7 +26,10 @@ final class Each extends FilteredNonEmptyArray /** @param non-empty-array $input */ protected function evaluateNonEmptyArray(array $input): Result { - $children = array_map(fn ($item) => $this->rule->evaluate($item), $input); + $children = []; + foreach ($input as $key => $value) { + $children[] = $this->rule->evaluate($value)->withPath($key); + } $isValid = array_reduce($children, static fn ($carry, $childResult) => $carry && $childResult->isValid, true); if ($isValid) { return Result::passed($input, $this)->withChildren(...$children); diff --git a/library/Rules/Key.php b/library/Rules/Key.php index 81b48e267..e30b85e4f 100644 --- a/library/Rules/Key.php +++ b/library/Rules/Key.php @@ -41,7 +41,7 @@ public function evaluate(mixed $input): Result return $this->rule ->evaluate($input[$this->key]) - ->withUnchangeableId((string) $this->key) + ->withPath($this->key) ->withNameIfMissing($this->rule->getName() ?? (string) $this->key); } } diff --git a/library/Rules/KeyOptional.php b/library/Rules/KeyOptional.php index 36d030749..4284aba0b 100644 --- a/library/Rules/KeyOptional.php +++ b/library/Rules/KeyOptional.php @@ -41,7 +41,7 @@ public function evaluate(mixed $input): Result return $this->rule ->evaluate($input[$this->key]) - ->withUnchangeableId((string) $this->key) + ->withPath($this->key) ->withNameIfMissing($this->rule->getName() ?? (string) $this->key); } } diff --git a/library/Rules/Length.php b/library/Rules/Length.php index b6f799bfe..bf57aecb0 100644 --- a/library/Rules/Length.php +++ b/library/Rules/Length.php @@ -59,7 +59,7 @@ private function enrichResult(mixed $input, Result $result): Result } return (new Result($result->isValid, $input, $this, id: $result->id)) - ->withPrefixedId('length') + ->withPrefix('length') ->withSubsequent($result->withInput($input)); } diff --git a/library/Rules/Max.php b/library/Rules/Max.php index 3f2cf62bc..1baaea382 100644 --- a/library/Rules/Max.php +++ b/library/Rules/Max.php @@ -26,9 +26,10 @@ final class Max extends FilteredNonEmptyArray /** @param non-empty-array $input */ protected function evaluateNonEmptyArray(array $input): Result { - $result = $this->rule->evaluate(max($input))->withPrefixedId('max'); + $result = $this->rule->evaluate(max($input))->withPrefix('max'); $template = $this->getName() === null ? self::TEMPLATE_STANDARD : self::TEMPLATE_NAMED; - return (new Result($result->isValid, $input, $this, [], $template, id: $result->id))->withSubsequent($result); + return (new Result($result->isValid, $input, $this, [], $template, id: $result->id)) + ->withSubsequent($result); } } diff --git a/library/Rules/Min.php b/library/Rules/Min.php index 2180da2c6..08af60e3f 100644 --- a/library/Rules/Min.php +++ b/library/Rules/Min.php @@ -26,7 +26,7 @@ final class Min extends FilteredNonEmptyArray /** @param non-empty-array $input */ protected function evaluateNonEmptyArray(array $input): Result { - $result = $this->rule->evaluate(min($input))->withPrefixedId('min'); + $result = $this->rule->evaluate(min($input))->withPrefix('min'); $template = $this->getName() === null ? self::TEMPLATE_STANDARD : self::TEMPLATE_NAMED; return (new Result($result->isValid, $input, $this, [], $template, id: $result->id))->withSubsequent($result); diff --git a/library/Rules/Not.php b/library/Rules/Not.php index 33cb3cc0a..7221c33b1 100644 --- a/library/Rules/Not.php +++ b/library/Rules/Not.php @@ -18,6 +18,6 @@ final class Not extends Wrapper { public function evaluate(mixed $input): Result { - return $this->rule->evaluate($input)->withInvertedMode()->withPrefixedId('not'); + return $this->rule->evaluate($input)->withInvertedMode()->withPrefix('not'); } } diff --git a/library/Rules/NullOr.php b/library/Rules/NullOr.php index 91af36c9d..51912fa5f 100644 --- a/library/Rules/NullOr.php +++ b/library/Rules/NullOr.php @@ -41,7 +41,7 @@ private function enrichResult(Result $result): Result { if ($result->allowsSubsequent()) { return $result - ->withPrefixedId('nullOr') + ->withPrefix('nullOr') ->withSubsequent(new Result($result->isValid, $result->input, $this)); } diff --git a/library/Rules/Property.php b/library/Rules/Property.php index ea057540c..d1e83e60a 100644 --- a/library/Rules/Property.php +++ b/library/Rules/Property.php @@ -38,7 +38,7 @@ public function evaluate(mixed $input): Result return $this->rule ->evaluate($this->extractPropertyValue($input, $this->propertyName)) - ->withUnchangeableId($this->propertyName) + ->withPath($this->propertyName) ->withNameIfMissing($this->rule->getName() ?? $this->propertyName); } } diff --git a/library/Rules/PropertyOptional.php b/library/Rules/PropertyOptional.php index 88cd90eda..8e867fbfb 100644 --- a/library/Rules/PropertyOptional.php +++ b/library/Rules/PropertyOptional.php @@ -38,7 +38,7 @@ public function evaluate(mixed $input): Result return $this->rule ->evaluate($this->extractPropertyValue($input, $this->propertyName)) - ->withUnchangeableId($this->propertyName) + ->withPath($this->propertyName) ->withNameIfMissing($this->rule->getName() ?? $this->propertyName); } } diff --git a/library/Rules/Size.php b/library/Rules/Size.php index 8ddca3058..d90dbcd44 100644 --- a/library/Rules/Size.php +++ b/library/Rules/Size.php @@ -110,7 +110,7 @@ private function enrichResult(mixed $input, Result $result): Result $parameters = ['unit' => self::DATA_STORAGE_UNITS[$this->unit]['name']]; return (new Result($result->isValid, $input, $this, $parameters, id: $result->id)) - ->withPrefixedId('size') + ->withPrefix('size') ->withSubsequent($result->withInput($input)); } } diff --git a/library/Rules/UndefOr.php b/library/Rules/UndefOr.php index 2938e2b08..97bd66b75 100644 --- a/library/Rules/UndefOr.php +++ b/library/Rules/UndefOr.php @@ -44,7 +44,7 @@ private function enrichResult(Result $result): Result { if ($result->allowsSubsequent()) { return $result - ->withPrefixedId('undefOr') + ->withPrefix('undefOr') ->withSubsequent(new Result($result->isValid, $result->input, $this)); } diff --git a/tests/feature/Issues/Issue1033Test.php b/tests/feature/Issues/Issue1033Test.php index 9914ebac8..246c30cc1 100644 --- a/tests/feature/Issues/Issue1033Test.php +++ b/tests/feature/Issues/Issue1033Test.php @@ -18,8 +18,8 @@ FULL_MESSAGE, [ '__root__' => 'Each item in `["A", "B", "B"]` must be valid', - 'equals.1' => '"A" must be equal to 1', - 'equals.2' => '"B" must be equal to 1', - 'equals.3' => '"B" must be equal to 1', + 0 => '"A" must be equal to 1', + 1 => '"B" must be equal to 1', + 2 => '"B" must be equal to 1', ] )); diff --git a/tests/feature/Issues/Issue1289Test.php b/tests/feature/Issues/Issue1289Test.php index 74722ef60..aa0babbdc 100644 --- a/tests/feature/Issues/Issue1289Test.php +++ b/tests/feature/Issues/Issue1289Test.php @@ -54,7 +54,7 @@ - description must be a string value FULL_MESSAGE, [ - 'allOf' => [ + 0 => [ '__root__' => 'These rules must pass for `["default": 2, "description": [], "children": ["nope"]]`', 'default' => [ '__root__' => 'Only one of these rules must pass for default', diff --git a/tests/feature/Issues/Issue1334Test.php b/tests/feature/Issues/Issue1334Test.php index 253115eb4..592e00b62 100644 --- a/tests/feature/Issues/Issue1334Test.php +++ b/tests/feature/Issues/Issue1334Test.php @@ -35,15 +35,23 @@ function (): void { - street must be a string FULL_MESSAGE, [ - 'each' => [ - '__root__' => 'Each item in `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]` must be valid', - 'allOf.1' => [ - '__root__' => 'These rules must pass for `["region": "Oregon", "country": "USA", "other": 123]`', - 'street' => 'street must be present', - 'other' => 'other must be a string or must be null', + '__root__' => 'These rules must pass for `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]`', + 0 => [ + '__root__' => 'These rules must pass for `["region": "Oregon", "country": "USA", "other": 123]`', + 'street' => 'street must be present', + 'other' => [ + 'nullOrStringType' => 'other must be a string or must be null', + ], + ], + 1 => [ + 'street' => [ + 'notEmpty' => 'street must not be empty', + ], + ], + 2 => [ + 'street' => [ + 'stringType' => 'street must be a string', ], - 'allOf.2' => 'street must not be empty', - 'allOf.3' => 'street must be a string', ], ] )); diff --git a/tests/feature/Issues/Issue1348Test.php b/tests/feature/Issues/Issue1348Test.php index 21becf343..f486fb140 100644 --- a/tests/feature/Issues/Issue1348Test.php +++ b/tests/feature/Issues/Issue1348Test.php @@ -47,35 +47,37 @@ - model must be in `["F150", "Bronco"]` FULL_MESSAGE, [ - 'each' => [ - '__root__' => 'Each item in `[["manufacturer": "Honda", "model": "Accord"], ["manufacturer": "Toyota", "model": "Rav4"], ["manufacturer": "Fo ... ]` must be valid', - 'oneOf.3' => [ - '__root__' => 'Only one of these rules must pass for `["manufacturer": "Ford", "model": "not real"]`', - 'allOf.1' => [ - '__root__' => 'All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`', - 'manufacturer' => 'manufacturer must be equal to "Honda"', - 'model' => 'model must be in `["Accord", "Fit"]`', - ], - 'allOf.2' => [ - '__root__' => 'All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`', - 'manufacturer' => 'manufacturer must be equal to "Toyota"', - 'model' => 'model must be in `["Rav4", "Camry"]`', - ], - 'allOf.3' => 'model must be in `["F150", "Bronco"]`', + '__root__' => 'These rules must pass for `[["manufacturer": "Honda", "model": "Accord"], ["manufacturer": "Toyota", "model": "Rav4"], ["manufacturer": "Fo ... ]`', + 0 => [ + '__root__' => 'Only one of these rules must pass for `["manufacturer": "Ford", "model": "not real"]`', + 'allOf.1' => [ + '__root__' => 'All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`', + 'manufacturer' => 'manufacturer must be equal to "Honda"', + 'model' => 'model must be in `["Accord", "Fit"]`', ], - 'oneOf.4' => [ - '__root__' => 'Only one of these rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', - 'allOf.1' => 'model must be in `["Accord", "Fit"]`', - 'allOf.2' => [ - '__root__' => 'All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', - 'manufacturer' => 'manufacturer must be equal to "Toyota"', - 'model' => 'model must be in `["Rav4", "Camry"]`', - ], - 'allOf.3' => [ - '__root__' => 'All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', - 'manufacturer' => 'manufacturer must be equal to "Ford"', - 'model' => 'model must be in `["F150", "Bronco"]`', - ], + 'allOf.2' => [ + '__root__' => 'All of the required rules must pass for `["manufacturer": "Ford", "model": "not real"]`', + 'manufacturer' => 'manufacturer must be equal to "Toyota"', + 'model' => 'model must be in `["Rav4", "Camry"]`', + ], + 'allOf.3' => [ + 'model' => 'model must be in `["F150", "Bronco"]`', + ], + ], + 1 => [ + '__root__' => 'Only one of these rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', + 'allOf.1' => [ + 'model' => 'model must be in `["Accord", "Fit"]`', + ], + 'allOf.2' => [ + '__root__' => 'All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', + 'manufacturer' => 'manufacturer must be equal to "Toyota"', + 'model' => 'model must be in `["Rav4", "Camry"]`', + ], + 'allOf.3' => [ + '__root__' => 'All of the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', + 'manufacturer' => 'manufacturer must be equal to "Ford"', + 'model' => 'model must be in `["F150", "Bronco"]`', ], ], ] diff --git a/tests/feature/Issues/Issue1469Test.php b/tests/feature/Issues/Issue1469Test.php index e9c3709da..a351b0558 100644 --- a/tests/feature/Issues/Issue1469Test.php +++ b/tests/feature/Issues/Issue1469Test.php @@ -22,10 +22,17 @@ function (): void { ]; v::arrayVal()->keySet( - v::key('order_items', v::arrayVal()->each(v::keySet( - v::key('product_title', v::stringVal()->notEmpty()), - v::key('quantity', v::intVal()->notEmpty()), - ))->notEmpty()), + v::key( + 'order_items', + v::arrayVal() + ->each( + v::keySet( + v::key('product_title', v::stringVal()->notEmpty()), + v::key('quantity', v::intVal()->notEmpty()), + ) + ) + ->notEmpty() + ), )->assert($data); }, 'quantity must be an integer value', @@ -40,9 +47,13 @@ function (): void { FULL_MESSAGE, [ 'keySet' => [ - '__root__' => 'Each item in order_items must be valid', - 'keySet.1' => 'quantity must be an integer value', - 'keySet.2' => [ + '__root__' => '`["order_items": [["product_title": test(?string $description = null, ?Closure $closure = null): Pest\Support\Hig ... ]` validation failed', + 0 => [ + 'quantity' => [ + 'intVal' => 'quantity must be an integer value', + ], + ], + 1 => [ '__root__' => 'order_items contains both missing and extra keys', 'product_title' => 'product_title must be present', 'quantity' => 'quantity must be present', diff --git a/tests/feature/Issues/Issue796Test.php b/tests/feature/Issues/Issue796Test.php index a53f00da7..8f3e2e1ca 100644 --- a/tests/feature/Issues/Issue796Test.php +++ b/tests/feature/Issues/Issue796Test.php @@ -50,7 +50,11 @@ FULL_MESSAGE, [ '__root__' => 'All of the required rules must pass for the given data', - 'mysql' => 'host must be a string', - 'postgresql' => 'user must be a string', + 'mysql' => [ + 'host' => 'host must be a string', + ], + 'postgresql' => [ + 'user' => 'user must be a string', + ], ] )); diff --git a/tests/feature/Rules/AttributesTest.php b/tests/feature/Rules/AttributesTest.php index c41edc807..c1f01ebcc 100644 --- a/tests/feature/Rules/AttributesTest.php +++ b/tests/feature/Rules/AttributesTest.php @@ -66,5 +66,9 @@ fn() => v::attributes()->assert(new WithAttributes('John Doe', 'john.doe@gmail.com', '22 years ago')), 'birthdate must be a valid date in the format "2005-12-30"', '- birthdate must be a valid date in the format "2005-12-30"', - ['birthdate' => 'birthdate must be a valid date in the format "2005-12-30"'] + [ + 'birthdate' => [ + 'date' => 'birthdate must be a valid date in the format "2005-12-30"', + ], + ] )); diff --git a/tests/feature/Rules/EachTest.php b/tests/feature/Rules/EachTest.php index b2f19a2a0..2f65d088e 100644 --- a/tests/feature/Rules/EachTest.php +++ b/tests/feature/Rules/EachTest.php @@ -32,9 +32,9 @@ FULL_MESSAGE, [ '__root__' => 'Each item in `["a", "b", "c"]` must be valid', - 'intType.1' => '"a" must be an integer', - 'intType.2' => '"b" must be an integer', - 'intType.3' => '"c" must be an integer', + 0 => '"a" must be an integer', + 1 => '"b" must be an integer', + 2 => '"c" must be an integer', ] )); @@ -49,9 +49,9 @@ FULL_MESSAGE, [ '__root__' => 'Each item in `[1, 2, 3]` must be invalid', - 'intType.1' => '1 must not be an integer', - 'intType.2' => '2 must not be an integer', - 'intType.3' => '3 must not be an integer', + 0 => '1 must not be an integer', + 1 => '2 must not be an integer', + 2 => '3 must not be an integer', ] )); @@ -80,9 +80,9 @@ FULL_MESSAGE, [ '__root__' => 'Each item in Wrapped must be valid', - 'intType.1' => 'Wrapped must be an integer', - 'intType.2' => 'Wrapped must be an integer', - 'intType.3' => 'Wrapped must be an integer', + 0 => 'Wrapped must be an integer', + 1 => 'Wrapped must be an integer', + 2 => 'Wrapped must be an integer', ] )); @@ -97,9 +97,9 @@ FULL_MESSAGE, [ '__root__' => 'Each item in Wrapped must be invalid', - 'intType.1' => 'Wrapped must not be an integer', - 'intType.2' => 'Wrapped must not be an integer', - 'intType.3' => 'Wrapped must not be an integer', + 0 => 'Wrapped must not be an integer', + 1 => 'Wrapped must not be an integer', + 2 => 'Wrapped must not be an integer', ] )); @@ -114,9 +114,9 @@ FULL_MESSAGE, [ '__root__' => 'Each item in Wrapper must be valid', - 'intType.1' => 'Wrapper must be an integer', - 'intType.2' => 'Wrapper must be an integer', - 'intType.3' => 'Wrapper must be an integer', + 0 => 'Wrapper must be an integer', + 1 => 'Wrapper must be an integer', + 2 => 'Wrapper must be an integer', ] )); @@ -131,9 +131,9 @@ FULL_MESSAGE, [ '__root__' => 'Each item in Wrapper must be invalid', - 'intType.1' => 'Wrapper must not be an integer', - 'intType.2' => 'Wrapper must not be an integer', - 'intType.3' => 'Wrapper must not be an integer', + 0 => 'Wrapper must not be an integer', + 1 => 'Wrapper must not be an integer', + 2 => 'Wrapper must not be an integer', ] )); @@ -148,9 +148,9 @@ FULL_MESSAGE, [ '__root__' => 'Each item in Not must be invalid', - 'intType.1' => 'Not must not be an integer', - 'intType.2' => 'Not must not be an integer', - 'intType.3' => 'Not must not be an integer', + 0 => 'Not must not be an integer', + 1 => 'Not must not be an integer', + 2 => 'Not must not be an integer', ] )); @@ -192,9 +192,9 @@ ->setTemplates([ 'each' => [ '__root__' => 'Here a sequence of items that did not pass the validation', - 'intType.1' => 'First item should have been an integer', - 'intType.2' => 'Second item should have been an integer', - 'intType.3' => 'Third item should have been an integer', + 0 => 'First item should have been an integer', + 1 => 'Second item should have been an integer', + 2 => 'Third item should have been an integer', ], ]) ->assert(['a', 'b', 'c']), @@ -207,9 +207,9 @@ FULL_MESSAGE, [ '__root__' => 'Here a sequence of items that did not pass the validation', - 'intType.1' => 'First item should have been an integer', - 'intType.2' => 'Second item should have been an integer', - 'intType.3' => 'Third item should have been an integer', + 0 => 'First item should have been an integer', + 1 => 'Second item should have been an integer', + 2 => 'Third item should have been an integer', ] )); @@ -219,23 +219,23 @@ ->setTemplates([ 'Wrapped' => [ '__root__' => 'Here a sequence of items that did not pass the validation', - 'Wrapped.1' => 'First item should have been an integer', - 'Wrapped.2' => 'Second item should have been an integer', - 'Wrapped.3' => 'Third item should have been an integer', + 0 => 'First item should have been an integer', + 1 => 'Second item should have been an integer', + 2 => 'Third item should have been an integer', ], ]) ->assert(['a', 'b', 'c']), - 'Wrapped must be an integer', + 'First item should have been an integer', <<<'FULL_MESSAGE' - - Each item in Wrapped must be valid - - Wrapped must be an integer - - Wrapped must be an integer - - Wrapped must be an integer + - Here a sequence of items that did not pass the validation + - First item should have been an integer + - Second item should have been an integer + - Third item should have been an integer FULL_MESSAGE, [ - '__root__' => 'Each item in Wrapped must be valid', - 'intType.1' => 'Wrapped must be an integer', - 'intType.2' => 'Wrapped must be an integer', - 'intType.3' => 'Wrapped must be an integer', + '__root__' => 'Here a sequence of items that did not pass the validation', + 0 => 'First item should have been an integer', + 1 => 'Second item should have been an integer', + 2 => 'Third item should have been an integer', ] )); diff --git a/tests/feature/Rules/PropertyTest.php b/tests/feature/Rules/PropertyTest.php index 0833cf719..963396131 100644 --- a/tests/feature/Rules/PropertyTest.php +++ b/tests/feature/Rules/PropertyTest.php @@ -21,6 +21,23 @@ ['foo' => 'foo must be an integer'] )); +test('With multiple rules', expectAll( + fn() => v::property('foo', v::intType()->positive())->assert((object) ['foo' => 'string']), + 'foo must be an integer', + <<<'FULL_MESSAGE' + - All of the required rules must pass for foo + - foo must be an integer + - foo must be a positive number + FULL_MESSAGE, + [ + 'foo' => [ + '__root__' => 'All of the required rules must pass for foo', + 'intType' => 'foo must be an integer', + 'positive' => 'foo must be a positive number', + ], + ] +)); + test('Inverted', expectAll( fn() => v::not(v::property('foo', v::intType()))->assert((object) ['foo' => 12]), 'foo must not be an integer', diff --git a/tests/unit/Rules/NotTest.php b/tests/unit/Rules/NotTest.php index c5d5201f3..85339f171 100644 --- a/tests/unit/Rules/NotTest.php +++ b/tests/unit/Rules/NotTest.php @@ -28,7 +28,7 @@ public function shouldInvertTheResultOfWrappedRule(): void self::assertEquals( $rule->evaluate('input'), - $wrapped->evaluate('input')->withPrefixedId('not')->withInvertedMode() + $wrapped->evaluate('input')->withPrefix('not')->withInvertedMode() ); }