diff --git a/library/Message/StandardFormatter.php b/library/Message/StandardFormatter.php index d3d9a6b7d..7f60480f6 100644 --- a/library/Message/StandardFormatter.php +++ b/library/Message/StandardFormatter.php @@ -35,7 +35,7 @@ public function __construct( } /** - * @param array $templates + * @param array $templates */ public function main(Result $result, array $templates, Translator $translator): string { @@ -50,7 +50,7 @@ public function main(Result $result, array $templates, Translator $translator): } /** - * @param array $templates + * @param array $templates */ public function full( Result $result, @@ -91,43 +91,46 @@ public function full( } /** - * @param array $templates + * @param array $templates * - * @return array + * @return array */ public function array(Result $result, array $templates, Translator $translator): array { $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), - ]; - } - $messages = []; + $messages = [ + '__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator), + ]; + + $children = []; + foreach ($deduplicatedChildren as $child) { - $messages[$child->id] = $this->array( + $childKey = $child->path ?? $child->id; + + $children[$childKey] = $this->array( $child, $this->selectTemplates($child, $selectedTemplates), $translator ); - if (count($messages[$child->id]) !== 1) { + + if (count($children[$childKey]) !== 1) { continue; } - $messages[$child->id] = current($messages[$child->id]); + $children[$childKey] = current($children[$childKey]); } - if (count($messages) > 1) { - $self = [ - '__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator), - ]; + if (count($children) === 0 || $this->isFinalTemplate($result, $selectedTemplates)) { + return [$result->path ?? $result->id => $messages['__root__']]; + } - return $self + $messages; + if ($result->path !== null) { + return [$result->path => $messages + $children]; } - return $messages; + return $messages + $children; } private function isAlwaysVisible(Result $result, Result ...$siblings): bool @@ -165,56 +168,69 @@ private function isAlwaysVisible(Result $result, Result ...$siblings): bool ); } - /** @param array $templates */ + /** @param array $templates */ private function getTemplated(Result $result, array $templates): Result { if ($result->hasCustomTemplate()) { return $result; } - if (!isset($templates[$result->id]) && isset($templates['__root__'])) { - return $result->withTemplate($templates['__root__']); + $keys = [$result->name, $result->path, $result->id, '__root__']; + foreach ($keys as $key) { + if (isset($templates[$key]) && is_string($templates[$key])) { + return $result->withTemplate($templates[$key]); + } } - if (!isset($templates[$result->id])) { + if (!isset($templates[$result->id]) && !isset($templates[$result->path]) && !isset($templates[$result->name])) { return $result; } - $template = $templates[$result->id]; - if (is_string($template)) { - return $result->withTemplate($template); - } - 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) + ) ); } /** - * @param array $templates + * @param array $templates */ 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__']); } /** - * @param array $templates + * @param array $templates * - * @return array + * @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 +243,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 +252,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 f2905f815..8467a13f2 100644 --- a/library/Result.php +++ b/library/Result.php @@ -40,7 +40,7 @@ public function __construct( ?string $name = null, ?string $id = null, public readonly ?Result $adjacent = null, - public readonly bool $unchangeableId = false, + public readonly string|int|null $path = null, Result ...$children, ) { $this->name = $rule->getName() ?? $name; @@ -99,21 +99,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 withPrefix(string $prefix): self { - if ($this->id === $this->name || $this->unchangeableId) { + if ($this->id === $this->name || $this->path !== null) { return $this; } @@ -200,7 +196,7 @@ private function clone( ?string $name = null, ?string $id = null, ?Result $adjacent = null, - ?bool $unchangeableId = null, + string|int|null $path = null, ?array $children = null ): self { return new self( @@ -213,7 +209,7 @@ private function clone( $name ?? $this->name, $id ?? $this->id, $adjacent ?? $this->adjacent, - $unchangeableId ?? $this->unchangeableId, + $path ?? $this->path, ...($children ?? $this->children) ); } diff --git a/library/Rules/Each.php b/library/Rules/Each.php index 66f4b8108..acc2bb372 100644 --- a/library/Rules/Each.php +++ b/library/Rules/Each.php @@ -28,7 +28,7 @@ protected function evaluateNonEmptyArray(array $input): Result { $children = []; foreach ($input as $key => $value) { - $children[] = $this->rule->evaluate($value)->withUnchangeableId((string) $key); + $children[] = $this->rule->evaluate($value)->withPath($key); } $isValid = array_reduce($children, static fn ($carry, $childResult) => $carry && $childResult->isValid, true); if ($isValid) { 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/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/Validator.php b/library/Validator.php index 06ba7146c..23ebee95c 100644 --- a/library/Validator.php +++ b/library/Validator.php @@ -29,7 +29,7 @@ final class Validator implements Rule /** @var array */ private array $rules = []; - /** @var array */ + /** @var array */ private array $templates = []; private ?string $name = null; @@ -65,7 +65,7 @@ public function isValid(mixed $input): bool return $this->evaluate($input)->isValid; } - /** @param array|callable(ValidationException): Throwable|string|Throwable|null $template */ + /** @param array|callable(ValidationException): Throwable|string|Throwable|null $template */ public function assert(mixed $input, array|string|Throwable|callable|null $template = null): void { $result = $this->evaluate($input); @@ -99,7 +99,7 @@ public function assert(mixed $input, array|string|Throwable|callable|null $templ throw $template($exception); } - /** @param array $templates */ + /** @param array $templates */ public function setTemplates(array $templates): self { $this->templates = $templates; diff --git a/tests/feature/Issues/Issue1289Test.php b/tests/feature/Issues/Issue1289Test.php index aa0babbdc..9edf422d7 100644 --- a/tests/feature/Issues/Issue1289Test.php +++ b/tests/feature/Issues/Issue1289Test.php @@ -54,6 +54,7 @@ - description must be a string value FULL_MESSAGE, [ + '__root__' => 'Each item in `[["default": 2, "description": [], "children": ["nope"]]]` must be valid', 0 => [ '__root__' => 'These rules must pass for `["default": 2, "description": [], "children": ["nope"]]`', 'default' => [ diff --git a/tests/feature/Issues/Issue1334Test.php b/tests/feature/Issues/Issue1334Test.php index d61f2933a..411a55c1b 100644 --- a/tests/feature/Issues/Issue1334Test.php +++ b/tests/feature/Issues/Issue1334Test.php @@ -35,15 +35,31 @@ function (): void { - street must be a string FULL_MESSAGE, [ + '__root__' => 'These rules must pass for `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]`', 'each' => [ '__root__' => 'Each item in `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]` must be valid', 0 => [ '__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', + 'other' => [ + '__root__' => 'These rules must pass for other', + 'nullOrStringType' => 'other must be a string or must be null', + ], + ], + 1 => [ + '__root__' => 'These rules must pass for `["street": "", "region": "Oregon", "country": "USA"]`', + 'street' => [ + '__root__' => 'These rules must pass for street', + 'notEmpty' => 'street must not be empty', + ], + ], + 2 => [ + '__root__' => 'These rules must pass for `["street": 123, "region": "Oregon", "country": "USA"]`', + 'street' => [ + '__root__' => 'These rules must pass for street', + 'stringType' => 'street must be a string', + ], ], - 1 => 'street must not be empty', - 2 => 'street must be a string', ], ] )); diff --git a/tests/feature/Issues/Issue1348Test.php b/tests/feature/Issues/Issue1348Test.php index ced0cef7a..4f9797c0f 100644 --- a/tests/feature/Issues/Issue1348Test.php +++ b/tests/feature/Issues/Issue1348Test.php @@ -47,6 +47,7 @@ - model must be in `["F150", "Bronco"]` FULL_MESSAGE, [ + '__root__' => 'These rules must pass for `[["manufacturer": "Honda", "model": "Accord"], ["manufacturer": "Toyota", "model": "Rav4"], ["manufacturer": "Fo ... ]`', 'each' => [ '__root__' => 'Each item in `[["manufacturer": "Honda", "model": "Accord"], ["manufacturer": "Toyota", "model": "Rav4"], ["manufacturer": "Fo ... ]` must be valid', 2 => [ @@ -61,11 +62,17 @@ 'manufacturer' => 'manufacturer must be equal to "Toyota"', 'model' => 'model must be in `["Rav4", "Camry"]`', ], - 'allOf.3' => 'model must be in `["F150", "Bronco"]`', + 'allOf.3' => [ + '__root__' => 'These rules must pass for `["manufacturer": "Ford", "model": "not real"]`', + 'model' => 'model must be in `["F150", "Bronco"]`', + ], ], 3 => [ '__root__' => 'Only one of these rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', - 'allOf.1' => 'model must be in `["Accord", "Fit"]`', + 'allOf.1' => [ + '__root__' => 'These rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', + 'model' => 'model must be in `["Accord", "Fit"]`', + ], 'allOf.2' => [ '__root__' => 'All the required rules must pass for `["manufacturer": "Honda", "model": "not valid"]`', 'manufacturer' => 'manufacturer must be equal to "Toyota"', diff --git a/tests/feature/Issues/Issue1469Test.php b/tests/feature/Issues/Issue1469Test.php index b3756a809..b4b9e00b2 100644 --- a/tests/feature/Issues/Issue1469Test.php +++ b/tests/feature/Issues/Issue1469Test.php @@ -39,14 +39,24 @@ function (): void { - product_title2 must not be present FULL_MESSAGE, [ + '__root__' => 'These rules must pass for `["order_items": [["product_title": test(?string $description = null, ?Closure $closure = null): Pest\Support\Hig ... ]`', 'keySet' => [ - '__root__' => 'Each item in order_items must be valid', - 0 => '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', - 'product_title2' => 'product_title2 must not be present', + '__root__' => '`["order_items": [["product_title": test(?string $description = null, ?Closure $closure = null): Pest\Support\Hig ... ]` validation failed', + 'each' => [ + '__root__' => 'Each item in order_items must be valid', + 0 => [ + '__root__' => 'order_items validation failed', + 'quantity' => [ + '__root__' => 'These rules must pass for 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', + 'product_title2' => 'product_title2 must not be present', + ], ], ], ] diff --git a/tests/feature/Issues/Issue425Test.php b/tests/feature/Issues/Issue425Test.php index 096026b17..a061afa92 100644 --- a/tests/feature/Issues/Issue425Test.php +++ b/tests/feature/Issues/Issue425Test.php @@ -16,5 +16,8 @@ function (): void { }, 'reference must be present', '- reference must be present', - ['reference' => 'reference must be present'] + [ + '__root__' => 'These rules must pass for `["age": 1]`', + 'reference' => 'reference must be present', + ] )); diff --git a/tests/feature/Issues/Issue446Test.php b/tests/feature/Issues/Issue446Test.php index c99c54691..64411d6ea 100644 --- a/tests/feature/Issues/Issue446Test.php +++ b/tests/feature/Issues/Issue446Test.php @@ -19,5 +19,8 @@ ->assert($arr), 'The length of name must be between 2 and 32', '- The length of name must be between 2 and 32', - ['name' => 'The length of name must be between 2 and 32'] + [ + '__root__' => 'These rules must pass for `["name": "w", "email": "hello@hello.com"]`', + 'name' => 'The length of name must be between 2 and 32', + ] )); diff --git a/tests/feature/Issues/Issue796Test.php b/tests/feature/Issues/Issue796Test.php index 24519b018..c531577a4 100644 --- a/tests/feature/Issues/Issue796Test.php +++ b/tests/feature/Issues/Issue796Test.php @@ -50,7 +50,13 @@ FULL_MESSAGE, [ '__root__' => 'All the required rules must pass for the given data', - 'mysql' => 'host must be a string', - 'postgresql' => 'user must be a string', + 'mysql' => [ + '__root__' => 'These rules must pass for mysql', + 'host' => 'host must be a string', + ], + 'postgresql' => [ + '__root__' => 'These rules must pass for postgresql', + 'user' => 'user must be a string', + ], ] )); diff --git a/tests/feature/Issues/Issue799Test.php b/tests/feature/Issues/Issue799Test.php index e93c76dbb..5e114c596 100644 --- a/tests/feature/Issues/Issue799Test.php +++ b/tests/feature/Issues/Issue799Test.php @@ -42,5 +42,8 @@ function ($url) { ->assert($input), 'scheme must start with "https"', '- scheme must start with "https"', - ['scheme' => 'scheme must start with "https"'] + [ + '__root__' => 'These rules must pass for `["scheme": "http", "host": "www.google.com", "path": "/search", "query": "q=respect.github.com"]`', + 'scheme' => 'scheme must start with "https"', + ] )); diff --git a/tests/feature/Rules/AllOfTest.php b/tests/feature/Rules/AllOfTest.php index 32e364660..d2069c1b5 100644 --- a/tests/feature/Rules/AllOfTest.php +++ b/tests/feature/Rules/AllOfTest.php @@ -41,7 +41,10 @@ fn() => v::allOf(v::not(v::intType()), v::greaterThan(2))->assert(4), '4 must not be an integer', '- 4 must not be an integer', - ['notIntType' => '4 must not be an integer'] + [ + '__root__' => 'These rules must pass for 4', + 'notIntType' => '4 must not be an integer', + ] )); test('With a single template', expectAll( diff --git a/tests/feature/Rules/AttributesTest.php b/tests/feature/Rules/AttributesTest.php index e853b66a3..da23a4cc3 100644 --- a/tests/feature/Rules/AttributesTest.php +++ b/tests/feature/Rules/AttributesTest.php @@ -13,14 +13,20 @@ fn() => v::attributes()->assert(new WithAttributes('', 'john.doe@gmail.com', '2024-06-23')), 'name must not be empty', '- name must not be empty', - ['name' => 'name must not be empty'] + [ + '__root__' => 'These rules must pass for `Respect\Validation\Test\Stubs\WithAttributes { +$name="" +$email="john.doe@gmail.com" +$birthdate="2024-06-23" + ... }`', + 'name' => 'name must not be empty', + ] )); test('Inverted', expectAll( fn() => v::attributes()->assert(new WithAttributes('John Doe', 'john.doe@gmail.com', '2024-06-23', '+1234567890')), 'phone must be a valid telephone number or must be null', '- phone must be a valid telephone number or must be null', - ['phone' => 'phone must be a valid telephone number or must be null'] + [ + '__root__' => 'These rules must pass for `Respect\Validation\Test\Stubs\WithAttributes { +$name="John Doe" +$email="john.doe@gmail.com" +$birthdate="2024- ... }`', + 'phone' => 'phone must be a valid telephone number or must be null', + ] )); test('Not an object', expectAll( @@ -34,7 +40,10 @@ fn() => v::attributes()->assert(new WithAttributes('John Doe', 'john.doe@gmail.com', '2024-06-23', 'not a phone number')), 'phone must be a valid telephone number or must be null', '- phone must be a valid telephone number or must be null', - ['phone' => 'phone must be a valid telephone number or must be null'] + [ + '__root__' => 'These rules must pass for `Respect\Validation\Test\Stubs\WithAttributes { +$name="John Doe" +$email="john.doe@gmail.com" +$birthdate="2024- ... }`', + 'phone' => 'phone must be a valid telephone number or must be null', + ] )); test('Multiple attributes, all failed', expectAll( @@ -66,5 +75,11 @@ 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"'] + [ + '__root__' => 'These rules must pass for `Respect\Validation\Test\Stubs\WithAttributes { +$name="John Doe" +$email="john.doe@gmail.com" +$birthdate="22 ye ... }`', + 'birthdate' => [ + '__root__' => 'These rules must pass for 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 e63ec8b25..ee1f78249 100644 --- a/tests/feature/Rules/EachTest.php +++ b/tests/feature/Rules/EachTest.php @@ -225,18 +225,18 @@ ], ]) ->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', - 0 => 'Wrapped must be an integer', - 1 => 'Wrapped must be an integer', - 2 => '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', ] )); @@ -282,8 +282,17 @@ FULL_MESSAGE, [ '__root__' => 'Each item in `[["not_int": "wrong"], ["my_int": 2], "not an array"]` must be valid', - 0 => 'my_int must be present', - 1 => 'my_int must be an odd number', + 0 => [ + '__root__' => 'These rules must pass for `["not_int": "wrong"]`', + 'my_int' => 'my_int must be present', + ], + 1 => [ + '__root__' => 'These rules must pass for `["my_int": 2]`', + 'my_int' => [ + '__root__' => 'These rules must pass for my_int', + 'odd' => 'my_int must be an odd number', + ], + ], 2 => [ '__root__' => 'All the required rules must pass for "not an array"', 'arrayType' => '"not an array" must be an array', diff --git a/tests/feature/Rules/KeySetTest.php b/tests/feature/Rules/KeySetTest.php index 836759cd1..85b7a8f17 100644 --- a/tests/feature/Rules/KeySetTest.php +++ b/tests/feature/Rules/KeySetTest.php @@ -11,21 +11,30 @@ fn() => v::keySet(v::key('foo', v::intType()))->assert(['foo' => 'string']), 'foo must be an integer', '- foo must be an integer', - ['foo' => 'foo must be an integer'] + [ + '__root__' => '`["foo": "string"]` validation failed', + 'foo' => 'foo must be an integer', + ] )); test('one rule / one missing key', expectAll( fn() => v::keySet(v::keyExists('foo'))->assert([]), 'foo must be present', '- foo must be present', - ['foo' => 'foo must be present'] + [ + '__root__' => '`[]` contains missing keys', + 'foo' => 'foo must be present', + ] )); test('one rule / one extra key', expectAll( fn() => v::keySet(v::keyExists('foo'))->assert(['foo' => 42, 'bar' => 'string']), 'bar must not be present', '- bar must not be present', - ['bar' => 'bar must not be present'] + [ + '__root__' => '`["foo": 42, "bar": "string"]` contains extra keys', + 'bar' => 'bar must not be present', + ] )); test('one rule / one extra key / one missing key', expectAll( @@ -108,7 +117,10 @@ fn() => v::keySet(v::keyExists('foo'), v::keyExists('bar'))->assert(['foo' => 42]), 'bar must be present', '- bar must be present', - ['bar' => 'bar must be present'] + [ + '__root__' => '`["foo": 42]` contains missing keys', + 'bar' => 'bar must be present', + ] )); test('multiple rules / all failed', expectAll( @@ -133,7 +145,10 @@ )->assert(['foo' => 42, 'bar' => 'string', 'baz' => true]), 'baz must not be present', '- baz must not be present', - ['baz' => 'baz must not be present'] + [ + '__root__' => '`["foo": 42, "bar": "string", "baz": true]` contains extra keys', + 'baz' => 'baz must not be present', + ] )); test('multiple rules / one extra key / one missing', expectAll( diff --git a/tests/feature/Rules/PropertyTest.php b/tests/feature/Rules/PropertyTest.php index 0833cf719..8b2cd5f2b 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 the required rules must pass for foo + - foo must be an integer + - foo must be a positive number + FULL_MESSAGE, + [ + 'foo' => [ + '__root__' => 'All 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/library/Builders/ResultBuilder.php b/tests/library/Builders/ResultBuilder.php index d37a9f0b3..21ab87075 100644 --- a/tests/library/Builders/ResultBuilder.php +++ b/tests/library/Builders/ResultBuilder.php @@ -35,8 +35,6 @@ final class ResultBuilder private ?Result $adjacent = null; - private bool $unchangeableId = false; - /** @var array */ private array $children = []; @@ -57,7 +55,7 @@ public function build(): Result $this->name, $this->id, $this->adjacent, - $this->unchangeableId, + null, ...$this->children ); } diff --git a/tests/unit/Message/StandardFormatter/ArrayProvider.php b/tests/unit/Message/StandardFormatter/ArrayProvider.php index 695faa919..221f40c25 100644 --- a/tests/unit/Message/StandardFormatter/ArrayProvider.php +++ b/tests/unit/Message/StandardFormatter/ArrayProvider.php @@ -76,7 +76,10 @@ public static function provideForArray(): array [ '__root__' => '__parent_original__', '1st' => '__1st_original__', - '2nd' => '__2nd_1st_original__', + '2nd' => [ + '__root__' => '__2nd_original__', + '2nd_1st' => '__2nd_1st_original__', + ], '3rd' => '__3rd_original__', ], ], @@ -85,7 +88,10 @@ public static function provideForArray(): array [ '__root__' => 'Parent custom', '1st' => '1st custom', - '2nd' => '2nd > 1st custom', + '2nd' => [ + '__root__' => '__2nd_original__', + '2nd_1st' => '2nd > 1st custom', + ], '3rd' => '3rd custom', ], [ @@ -102,7 +108,10 @@ public static function provideForArray(): array [ '__root__' => 'Parent custom', '1st' => '1st custom', - '2nd' => '__2nd_1st_original__', + '2nd' => [ + '__root__' => '__2nd_original__', + '2nd_1st' => '__2nd_1st_original__', + ], '3rd' => '3rd custom', ], [ diff --git a/tests/unit/Message/StandardFormatterTest.php b/tests/unit/Message/StandardFormatterTest.php index b03269896..88142fb40 100644 --- a/tests/unit/Message/StandardFormatterTest.php +++ b/tests/unit/Message/StandardFormatterTest.php @@ -49,12 +49,12 @@ public function itShouldThrowAnExceptionWhenTryingToFormatAsMainAndTemplateIsInv $renderer = new StandardFormatter(new TestingMessageRenderer()); $result = (new ResultBuilder())->id('foo')->build(); - $template = new stdClass(); + $template = ['foo' => new stdClass()]; $this->expectException(ComponentException::class); $this->expectExceptionMessage(sprintf('Template for "foo" must be a string, %s given', stringify($template))); - $renderer->main($result, ['foo' => $template], new DummyTranslator()); + $renderer->main($result, $template, new DummyTranslator()); } /** @param array $templates */ @@ -73,12 +73,12 @@ public function itShouldThrowAnExceptionWhenTryingToFormatAsFullAndTemplateIsInv $renderer = new StandardFormatter(new TestingMessageRenderer()); $result = (new ResultBuilder())->id('foo')->build(); - $template = new stdClass(); + $template = ['foo' => new stdClass()]; $this->expectException(ComponentException::class); $this->expectExceptionMessage(sprintf('Template for "foo" must be a string, %s given', stringify($template))); - $renderer->full($result, ['foo' => $template], new DummyTranslator()); + $renderer->full($result, $template, new DummyTranslator()); } /** @@ -100,11 +100,11 @@ public function itShouldThrowAnExceptionWhenTryingToFormatAsArrayAndTemplateIsIn $renderer = new StandardFormatter(new TestingMessageRenderer()); $result = (new ResultBuilder())->id('foo')->build(); - $template = new stdClass(); + $template = ['foo' => new stdClass()]; $this->expectException(ComponentException::class); $this->expectExceptionMessage(sprintf('Template for "foo" must be a string, %s given', stringify($template))); - $renderer->array($result, ['foo' => $template], new DummyTranslator()); + $renderer->array($result, $template, new DummyTranslator()); } }