Skip to content

Commit

Permalink
WIP: Improve messages in an array format
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
henriquemoody committed Dec 18, 2024
1 parent aa293de commit 399be98
Show file tree
Hide file tree
Showing 25 changed files with 219 additions and 136 deletions.
78 changes: 58 additions & 20 deletions library/Message/StandardFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
)
);
}

Expand All @@ -195,26 +222,37 @@ 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__']);
}

/**
* @param array<string, mixed> $templates
*
* @return array<string, mixed>
*/
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;
Expand All @@ -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)) {
Expand All @@ -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));
Expand Down
18 changes: 7 additions & 11 deletions library/Result.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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(
Expand All @@ -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)
);
}
Expand Down
2 changes: 1 addition & 1 deletion library/Rules/DateTimeDiff.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
6 changes: 4 additions & 2 deletions library/Rules/Each.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -27,7 +26,10 @@ final class Each extends FilteredNonEmptyArray
/** @param non-empty-array<mixed> $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);
Expand Down
2 changes: 1 addition & 1 deletion library/Rules/Key.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion library/Rules/KeyOptional.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion library/Rules/Length.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
5 changes: 3 additions & 2 deletions library/Rules/Max.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ final class Max extends FilteredNonEmptyArray
/** @param non-empty-array<mixed> $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);
}
}
2 changes: 1 addition & 1 deletion library/Rules/Min.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class Min extends FilteredNonEmptyArray
/** @param non-empty-array<mixed> $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);
Expand Down
2 changes: 1 addition & 1 deletion library/Rules/Not.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
2 changes: 1 addition & 1 deletion library/Rules/NullOr.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
2 changes: 1 addition & 1 deletion library/Rules/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion library/Rules/PropertyOptional.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion library/Rules/Size.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
2 changes: 1 addition & 1 deletion library/Rules/UndefOr.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
6 changes: 3 additions & 3 deletions tests/feature/Issues/Issue1033Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]
));
2 changes: 1 addition & 1 deletion tests/feature/Issues/Issue1289Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit 399be98

Please sign in to comment.