diff --git a/library/Message/StandardFormatter.php b/library/Message/StandardFormatter.php index 193caaa27..752a5acc8 100644 --- a/library/Message/StandardFormatter.php +++ b/library/Message/StandardFormatter.php @@ -14,6 +14,7 @@ use function array_filter; use function array_key_exists; +use function array_map; use function array_reduce; use function array_values; use function count; @@ -42,11 +43,7 @@ public function main(Result $result, array $templates, Translator $translator): $selectedTemplates = $this->selectTemplates($result, $templates); if (!$this->isFinalTemplate($result, $selectedTemplates)) { foreach ($this->extractDeduplicatedChildren($result) as $child) { - if ($result->path !== null && $child->path !== null && $child->path !== $result->path) { - $child = $child->withPath($result->path); - } elseif ($result->path !== null && $child->path === null) { - $child = $child->withPath($result->path); - } + $child = $this->resultWithPath($result, $child); return $this->main($child, $selectedTemplates, $translator); } @@ -63,7 +60,6 @@ public function full( array $templates, Translator $translator, int $depth = 0, - ?Result $parent = null, Result ...$siblings ): string { $selectedTemplates = $this->selectTemplates($result, $templates); @@ -75,20 +71,25 @@ public function full( $rendered .= sprintf( '%s- %s' . PHP_EOL, $indentation, - $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator), + $this->renderer->render( + $this->getTemplated($depth > 0 ? $result->withDeepestPath() : $result, $selectedTemplates), + $translator + ), ); $depth++; } if (!$isFinalTemplate) { - $results = $this->extractDeduplicatedChildren($result); + $results = array_map( + fn(Result $child) => $this->resultWithPath($result, $child), + $this->extractDeduplicatedChildren($result) + ); foreach ($results as $child) { $rendered .= $this->full( $child, $selectedTemplates, $translator, $depth, - $result, ...array_filter($results, static fn (Result $sibling) => $sibling !== $child) ); $rendered .= PHP_EOL; @@ -110,7 +111,7 @@ public function array(Result $result, array $templates, Translator $translator): if (count($deduplicatedChildren) === 0 || $this->isFinalTemplate($result, $selectedTemplates)) { return [ $result->path ?? $result->id => $this->renderer->render( - $this->getTemplated($result, $selectedTemplates), + $this->getTemplated($result->withDeepestPath(), $selectedTemplates), $translator ), ]; @@ -120,7 +121,7 @@ public function array(Result $result, array $templates, Translator $translator): foreach ($deduplicatedChildren as $child) { $key = $child->path ?? $child->id; $messages[$key] = $this->array( - $child, + $this->resultWithPath($result, $child), $this->selectTemplates($child, $selectedTemplates), $translator ); @@ -133,7 +134,10 @@ public function array(Result $result, array $templates, Translator $translator): if (count($messages) > 1) { $self = [ - '__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator), + '__root__' => $this->renderer->render( + $this->getTemplated($result->withDeepestPath(), $selectedTemplates), + $translator + ), ]; return $self + $messages; @@ -142,6 +146,19 @@ public function array(Result $result, array $templates, Translator $translator): return $messages; } + public function resultWithPath(Result $parent, Result $child): Result + { + if ($parent->path !== null && $child->path !== null && $child->path !== $parent->path) { + return $child->withPath($parent->path); + } + + if ($parent->path !== null && $child->path === null) { + return $child->withPath($parent->path); + } + + return $child; + } + private function isAlwaysVisible(Result $result, Result ...$siblings): bool { if ($result->isValid) { diff --git a/library/Message/StandardRenderer.php b/library/Message/StandardRenderer.php index 3c3c21be7..0dc8e7775 100644 --- a/library/Message/StandardRenderer.php +++ b/library/Message/StandardRenderer.php @@ -36,11 +36,12 @@ public function __construct( public function render(Result $result, Translator $translator, ?string $template = null): string { - $parameters = $result->parameters + [ - 'path' => $result->path !== null ? Quoted::fromPath($result->path) : null, - 'input' => $result->input, - ]; - $parameters['name'] ??= $result->name ?? $parameters['path'] ?? $this->placeholder('input', $result->input, $translator); + $parameters = $result->parameters; + $parameters['path'] = $result->path !== null ? Quoted::fromPath($result->path) : null; + $parameters['input'] = $result->input; + + $builtName = $result->name ?? $parameters['path'] ?? $this->placeholder('input', $result->input, $translator); + $parameters['name'] ??= $builtName; $rendered = (string) preg_replace_callback( '/{{(\w+)(\|([^}]+))?}}/', diff --git a/library/Result.php b/library/Result.php index 53d70c3b0..f1d433e45 100644 --- a/library/Result.php +++ b/library/Result.php @@ -10,11 +10,12 @@ namespace Respect\Validation; use Respect\Validation\Rules\Core\Nameable; -use Respect\Validation\Rules\Core\Renameable; use function array_filter; use function array_map; use function count; +use function end; +use function explode; use function lcfirst; use function preg_match; use function strrchr; @@ -112,17 +113,22 @@ public function withIdFrom(Rule $rule): self public function withPath(string|int $path): self { - if ($this->path === $path) { + return $this->clone( + adjacent: $this->adjacent?->withPath($path), + path: $this->path === null ? $path : $path . '.' . $this->path, + ); + } + + public function withDeepestPath(): self + { + $paths = explode('.', (string) $this->path); + if (count($paths) === 1) { return $this; } return $this->clone( - adjacent: $this->adjacent?->withPath($path), - path: $this->path === null ? $path : $path . '.' . $this->path, -// children: array_map( -// static fn (Result $child) => $child->path === null ? $child->withPath($child->name ?? $path) : $child, -// $this->children -// ), + adjacent: $this->adjacent?->withPath(end($paths)), + path: end($paths), ); } diff --git a/library/Rules/KeyExists.php b/library/Rules/KeyExists.php index 6c8f6509e..46d5e1164 100644 --- a/library/Rules/KeyExists.php +++ b/library/Rules/KeyExists.php @@ -14,7 +14,6 @@ use Respect\Validation\Message\Template; use Respect\Validation\Result; use Respect\Validation\Rules\Core\KeyRelated; -use Respect\Validation\Rules\Core\Renameable; use Respect\Validation\Rules\Core\Standard; use function array_key_exists; diff --git a/library/Rules/PropertyExists.php b/library/Rules/PropertyExists.php index d7ecc1d55..a3f00cdd6 100644 --- a/library/Rules/PropertyExists.php +++ b/library/Rules/PropertyExists.php @@ -13,7 +13,6 @@ use ReflectionObject; use Respect\Validation\Message\Template; use Respect\Validation\Result; -use Respect\Validation\Rules\Core\Renameable; use Respect\Validation\Rules\Core\Standard; use function is_object; diff --git a/tests/feature/Issues/Issue1289Test.php b/tests/feature/Issues/Issue1289Test.php index 2244dc953..240493d37 100644 --- a/tests/feature/Issues/Issue1289Test.php +++ b/tests/feature/Issues/Issue1289Test.php @@ -49,8 +49,8 @@ <<<'FULL_MESSAGE' - `.0` must pass the rules - `.default` must pass one of the rules - - 2 must be a string - - 2 must be a boolean + - `.default` must be a string + - `.default` must be a boolean - `.description` must be a string value FULL_MESSAGE, [ @@ -58,8 +58,8 @@ '__root__' => '`.0` must pass the rules', 'default' => [ '__root__' => '`.default` must pass one of the rules', - 'stringType' => '2 must be a string', - 'boolType' => '2 must be a boolean', + 'stringType' => '`.default` must be a string', + 'boolType' => '`.default` must be a boolean', ], 'description' => '`.description` must be a string value', ], diff --git a/tests/feature/Issues/Issue1334Test.php b/tests/feature/Issues/Issue1334Test.php index dd5e3cc58..c209effa1 100644 --- a/tests/feature/Issues/Issue1334Test.php +++ b/tests/feature/Issues/Issue1334Test.php @@ -28,11 +28,11 @@ function (): void { - `.0` must pass the rules - `.street` must be present - `.other` must pass the rules - - 123 must be a string or must be null + - `.other` must be a string or must be null - `.1` must pass the rules - - "" must not be empty + - `.street` must not be empty - `.2` must pass the rules - - 123 must be a string + - `.street` must be a string FULL_MESSAGE, [ 'each' => [ @@ -40,10 +40,10 @@ function (): void { 0 => [ '__root__' => '`.0` must pass the rules', 'street' => '`.street` must be present', - 'other' => '123 must be a string or must be null', + 'other' => '`.other` must be a string or must be null', ], - 1 => '"" must not be empty', - 2 => '123 must be a string', + 1 => '`.street` must not be empty', + 2 => '`.street` must be a string', ], ], )); diff --git a/tests/feature/Issues/Issue1376Test.php b/tests/feature/Issues/Issue1376Test.php index e8d37d56b..639089c52 100644 --- a/tests/feature/Issues/Issue1376Test.php +++ b/tests/feature/Issues/Issue1376Test.php @@ -20,8 +20,8 @@ - `.title` must be present - `.description` must be present - `.author` must pass all the rules - - "foo" must be an integer - - The length of "foo" must be between 1 and 2 + - `.author` must be an integer + - The length of `.author` must be between 1 and 2 - `.user` must be present FULL_MESSAGE, [ @@ -30,8 +30,8 @@ 'description' => '`.description` must be present', 'author' => [ '__root__' => '`.author` must pass all the rules', - 'intType' => '"foo" must be an integer', - 'lengthBetween' => 'The length of "foo" must be between 1 and 2', + 'intType' => '`.author` must be an integer', + 'lengthBetween' => 'The length of `.author` must be between 1 and 2', ], 'user' => '`.user` must be present', ], diff --git a/tests/feature/Issues/Issue1469Test.php b/tests/feature/Issues/Issue1469Test.php index ebe2b2721..ab0f3d86f 100644 --- a/tests/feature/Issues/Issue1469Test.php +++ b/tests/feature/Issues/Issue1469Test.php @@ -9,26 +9,35 @@ test('https://github.com/Respect/Validation/issues/1469', expectAll( function (): void { - $data = [ - 'order_items' => [ - [ - 'product_title' => 'test', - 'quantity' => 'test', + v::create() + ->arrayVal() + ->keySet( + v::key( + 'order_items', + v::create() + ->arrayVal() + ->each( + v::keySet( + v::key('product_title', v::stringVal()->notEmpty()), + v::key('quantity', v::intVal()->notEmpty()), + ) + ) + ->notEmpty() + ), + ) + ->assert([ + 'order_items' => [ + [ + 'product_title' => 'test', + 'quantity' => 'test', + ], + [ + 'product_title2' => 'test', + ], ], - [ - 'product_title2' => 'test', - ], - ], - ]; - - 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()), - )->assert($data); + ]); }, - '`.0.quantity` must be an integer value', + '`.order_items.0.quantity` must be an integer value', <<<'FULL_MESSAGE' - Each item in `.order_items` must be valid - `.0` validation failed @@ -45,9 +54,19 @@ function (): void { 1 => [ '__root__' => '`.order_items` contains both missing and extra keys', 'product_title' => '`.product_title` must be present', - 'quantity' => '`.quantity must` be present', + 'quantity' => '`.quantity` must be present', 'product_title2' => '`.product_title2` must not be present', ], ], ], -)); +))->skip(<< '`Respect\Validation\Test\Stubs\WithAttributes{ +$name="" +$email="not an email" +$birthdate="not a date" +$phone ... }` must pass all the rules', + '__root__' => '`Respect\Validation\Test\Stubs\WithAttributes { +$name="" +$email="not an email" +$birthdate="not a date" +$phone ... }` must pass all the rules', 'name' => '`.name` must not be empty', 'email' => '`.email` must be a valid email address', 'birthdate' => [ - '__root__' => 'birthdate must pass all the rules', + '__root__' => '`.birthdate` must pass all the rules', 'date' => '`.birthdate` must be a valid date in the format "2005-12-30"', 'dateTimeDiffLessThanOrEqual' => 'For comparison with now, `.birthdate` must be a valid datetime', ], diff --git a/tests/feature/Rules/EachTest.php b/tests/feature/Rules/EachTest.php index 820466e11..7476d19e6 100644 --- a/tests/feature/Rules/EachTest.php +++ b/tests/feature/Rules/EachTest.php @@ -256,13 +256,13 @@ '__root__' => 'Each item in `[2, 4]` must be valid', 0 => [ '__root__' => '`.0` must pass all the rules', - 'between' => '2 must be between 5 and 7', - 'odd' => '2 must be an odd number', + 'between' => '`.0` must be between 5 and 7', + 'odd' => '`.0` must be an odd number', ], 1 => [ '__root__' => '`.1` must pass all the rules', - 'between' => '4 must be between 5 and 7', - 'odd' => '4 must be an odd number', + 'between' => '`.1` must be between 5 and 7', + 'odd' => '`.1` must be an odd number', ], ], )); @@ -282,12 +282,12 @@ 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 => '`.my_int` must be present', + 1 => '`.my_int` must be an odd number', 2 => [ - '__root__' => '"not an array" must pass all the rules', - 'arrayType' => '"not an array" must be an array', - 'my_int' => 'my_int must be present', + '__root__' => '`.2` must pass all the rules', + 'arrayType' => '`.2` must be an array', + 'my_int' => '`.my_int` must be present', ], ], )); 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 ); }