Skip to content

Commit cad83ce

Browse files
committed
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.
1 parent 0e40917 commit cad83ce

23 files changed

+236
-99
lines changed

library/Message/StandardFormatter.php

Lines changed: 78 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414

1515
use function array_filter;
1616
use function array_key_exists;
17+
use function array_merge;
1718
use function array_reduce;
1819
use function array_values;
1920
use function count;
2021
use function current;
2122
use function is_array;
2223
use function is_string;
24+
use function key;
2325
use function Respect\Stringifier\stringify;
2426
use function rtrim;
2527
use function sprintf;
@@ -35,10 +37,14 @@ public function __construct(
3537
}
3638

3739
/**
38-
* @param array<string, mixed> $templates
40+
* @param array<string|int, mixed> $templates
3941
*/
4042
public function main(Result $result, array $templates, Translator $translator): string
4143
{
44+
if ($result->isValid) {
45+
return '';
46+
}
47+
4248
$selectedTemplates = $this->selectTemplates($result, $templates);
4349
if (!$this->isFinalTemplate($result, $selectedTemplates)) {
4450
foreach ($this->extractDeduplicatedChildren($result) as $child) {
@@ -50,7 +56,7 @@ public function main(Result $result, array $templates, Translator $translator):
5056
}
5157

5258
/**
53-
* @param array<string, mixed> $templates
59+
* @param array<string|int, mixed> $templates
5460
*/
5561
public function full(
5662
Result $result,
@@ -59,6 +65,10 @@ public function full(
5965
int $depth = 0,
6066
Result ...$siblings
6167
): string {
68+
if ($result->isValid) {
69+
return '';
70+
}
71+
6272
$selectedTemplates = $this->selectTemplates($result, $templates);
6373
$isFinalTemplate = $this->isFinalTemplate($result, $selectedTemplates);
6474

@@ -91,43 +101,58 @@ public function full(
91101
}
92102

93103
/**
94-
* @param array<string, mixed> $templates
104+
* @param array<string|int, mixed> $templates
95105
*
96-
* @return array<string, mixed>
106+
* @return array<string|int, mixed>
97107
*/
98108
public function array(Result $result, array $templates, Translator $translator): array
99109
{
110+
if ($result->isValid) {
111+
return [];
112+
}
113+
114+
100115
$selectedTemplates = $this->selectTemplates($result, $templates);
101116
$deduplicatedChildren = $this->extractDeduplicatedChildren($result);
102-
if (count($deduplicatedChildren) === 0 || $this->isFinalTemplate($result, $selectedTemplates)) {
103-
return [
104-
$result->id => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator),
105-
];
117+
118+
$key = $this->isFinalTemplate($result, $selectedTemplates) ? $result->path ?? $result->id : '__root__';
119+
$messages = [$key => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator)];
120+
121+
if ($key !== '__root__') {
122+
return $messages;
106123
}
107124

108-
$messages = [];
125+
$children = [];
126+
109127
foreach ($deduplicatedChildren as $child) {
110-
$messages[$child->id] = $this->array(
128+
if ($child->isValid) {
129+
continue;
130+
}
131+
$childKey = $child->path ?? $child->id;
132+
133+
$children[$childKey] = $this->array(
111134
$child,
112135
$this->selectTemplates($child, $selectedTemplates),
113136
$translator
114137
);
115-
if (count($messages[$child->id]) !== 1) {
138+
139+
if (count($children[$childKey]) !== 1) {
116140
continue;
117141
}
118142

119-
$messages[$child->id] = current($messages[$child->id]);
120-
}
143+
$grantChildKey = key($children[$childKey]);
144+
if ($grantChildKey != $childKey) {
145+
continue;
146+
}
121147

122-
if (count($messages) > 1) {
123-
$self = [
124-
'__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator),
125-
];
148+
$children[$childKey] = current($children[$childKey]);
149+
}
126150

127-
return $self + $messages;
151+
if (count($children) === 0 || $this->isFinalTemplate($result, $selectedTemplates)) {
152+
return [$result->path ?? $result->id => $messages['__root__']];
128153
}
129154

130-
return $messages;
155+
return $messages + $children;
131156
}
132157

133158
private function isAlwaysVisible(Result $result, Result ...$siblings): bool
@@ -165,56 +190,69 @@ private function isAlwaysVisible(Result $result, Result ...$siblings): bool
165190
);
166191
}
167192

168-
/** @param array<string, mixed> $templates */
193+
/** @param array<string|int, mixed> $templates */
169194
private function getTemplated(Result $result, array $templates): Result
170195
{
171196
if ($result->hasCustomTemplate()) {
172197
return $result;
173198
}
174199

175-
if (!isset($templates[$result->id]) && isset($templates['__root__'])) {
176-
return $result->withTemplate($templates['__root__']);
200+
$keys = [$result->name, $result->path, $result->id, '__root__'];
201+
foreach ($keys as $key) {
202+
if (isset($templates[$key]) && is_string($templates[$key])) {
203+
return $result->withTemplate($templates[$key]);
204+
}
177205
}
178206

179-
if (!isset($templates[$result->id])) {
207+
if (!isset($templates[$result->id]) && !isset($templates[$result->path]) && !isset($templates[$result->name])) {
180208
return $result;
181209
}
182210

183-
$template = $templates[$result->id];
184-
if (is_string($template)) {
185-
return $result->withTemplate($template);
186-
}
187-
188211
throw new ComponentException(
189-
sprintf('Template for "%s" must be a string, %s given', $result->id, stringify($template))
212+
sprintf(
213+
'Template for "%s" must be a string, %s given',
214+
$result->path ?? $result->name ?? $result->id,
215+
stringify($templates)
216+
)
190217
);
191218
}
192219

193220
/**
194-
* @param array<string, mixed> $templates
221+
* @param array<string|int, mixed> $templates
195222
*/
196223
private function isFinalTemplate(Result $result, array $templates): bool
197224
{
198-
if (isset($templates[$result->id]) && is_string($templates[$result->id])) {
199-
return true;
225+
$keys = [$result->name, $result->path, $result->id];
226+
foreach ($keys as $key) {
227+
if (isset($templates[$key]) && is_string($templates[$key])) {
228+
return true;
229+
}
200230
}
201231

202232
if (count($templates) !== 1) {
203233
return false;
204234
}
205235

206-
return isset($templates['__root__']) || isset($templates[$result->id]);
236+
foreach ($keys as $key) {
237+
if (isset($templates[$key])) {
238+
return true;
239+
}
240+
}
241+
242+
return isset($templates['__root__']);
207243
}
208244

209245
/**
210-
* @param array<string, mixed> $templates
246+
* @param array<string|int, mixed> $templates
211247
*
212-
* @return array<string, mixed>
248+
* @return array<string|int, mixed>
213249
*/
214-
private function selectTemplates(Result $message, array $templates): array
250+
private function selectTemplates(Result $result, array $templates): array
215251
{
216-
if (isset($templates[$message->id]) && is_array($templates[$message->id])) {
217-
return $templates[$message->id];
252+
foreach ([$result->name, $result->path, $result->id] as $key) {
253+
if (isset($templates[$key]) && is_array($templates[$key])) {
254+
return $templates[$key];
255+
}
218256
}
219257

220258
return $templates;
@@ -227,7 +265,7 @@ private function extractDeduplicatedChildren(Result $result): array
227265
$deduplicatedResults = [];
228266
$duplicateCounters = [];
229267
foreach ($result->children as $child) {
230-
$id = $child->id;
268+
$id = $child->path ?? $child->id;
231269
if (isset($duplicateCounters[$id])) {
232270
$id .= '.' . ++$duplicateCounters[$id];
233271
} elseif (array_key_exists($id, $deduplicatedResults)) {
@@ -236,7 +274,7 @@ private function extractDeduplicatedChildren(Result $result): array
236274
$duplicateCounters[$id] = 2;
237275
$id .= '.2';
238276
}
239-
$deduplicatedResults[$id] = $child->isValid ? null : $child->withId($id);
277+
$deduplicatedResults[$id] = $child->isValid ? null : $child->withId((string) $id);
240278
}
241279

242280
return array_values(array_filter($deduplicatedResults));

library/Result.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function __construct(
4040
?string $name = null,
4141
?string $id = null,
4242
public readonly ?Result $adjacent = null,
43-
public readonly bool $unchangeableId = false,
43+
public readonly string|int|null $path = null,
4444
Result ...$children,
4545
) {
4646
$this->name = $rule->getName() ?? $name;
@@ -99,21 +99,17 @@ public function withTemplate(string $template): self
9999

100100
public function withId(string $id): self
101101
{
102-
if ($this->unchangeableId) {
103-
return $this;
104-
}
105-
106102
return $this->clone(id: $id);
107103
}
108104

109-
public function withUnchangeableId(string $id): self
105+
public function withPath(string|int $path): self
110106
{
111-
return $this->clone(id: $id, unchangeableId: true);
107+
return $this->clone(path: $path);
112108
}
113109

114110
public function withPrefix(string $prefix): self
115111
{
116-
if ($this->id === $this->name || $this->unchangeableId) {
112+
if ($this->id === $this->name || $this->path !== null) {
117113
return $this;
118114
}
119115

@@ -200,7 +196,7 @@ private function clone(
200196
?string $name = null,
201197
?string $id = null,
202198
?Result $adjacent = null,
203-
?bool $unchangeableId = null,
199+
string|int|null $path = null,
204200
?array $children = null
205201
): self {
206202
return new self(
@@ -213,7 +209,7 @@ private function clone(
213209
$name ?? $this->name,
214210
$id ?? $this->id,
215211
$adjacent ?? $this->adjacent,
216-
$unchangeableId ?? $this->unchangeableId,
212+
$path ?? $this->path,
217213
...($children ?? $this->children)
218214
);
219215
}

library/Rules/Each.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ protected function evaluateNonEmptyArray(array $input): Result
2828
{
2929
$children = [];
3030
foreach ($input as $key => $value) {
31-
$children[] = $this->rule->evaluate($value)->withUnchangeableId((string) $key);
31+
$children[] = $this->rule->evaluate($value)->withPath($key);
3232
}
3333
$isValid = array_reduce($children, static fn ($carry, $childResult) => $carry && $childResult->isValid, true);
3434
if ($isValid) {

library/Rules/Key.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function evaluate(mixed $input): Result
4141

4242
return $this->rule
4343
->evaluate($input[$this->key])
44-
->withUnchangeableId((string) $this->key)
44+
->withPath($this->key)
4545
->withNameIfMissing($this->rule->getName() ?? (string) $this->key);
4646
}
4747
}

library/Rules/KeyOptional.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function evaluate(mixed $input): Result
4141

4242
return $this->rule
4343
->evaluate($input[$this->key])
44-
->withUnchangeableId((string) $this->key)
44+
->withPath($this->key)
4545
->withNameIfMissing($this->rule->getName() ?? (string) $this->key);
4646
}
4747
}

library/Rules/Property.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function evaluate(mixed $input): Result
3838

3939
return $this->rule
4040
->evaluate($this->extractPropertyValue($input, $this->propertyName))
41-
->withUnchangeableId($this->propertyName)
41+
->withPath($this->propertyName)
4242
->withNameIfMissing($this->rule->getName() ?? $this->propertyName);
4343
}
4444
}

library/Rules/PropertyOptional.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function evaluate(mixed $input): Result
3838

3939
return $this->rule
4040
->evaluate($this->extractPropertyValue($input, $this->propertyName))
41-
->withUnchangeableId($this->propertyName)
41+
->withPath($this->propertyName)
4242
->withNameIfMissing($this->rule->getName() ?? $this->propertyName);
4343
}
4444
}

library/Validator.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ final class Validator implements Rule
2929
/** @var array<Rule> */
3030
private array $rules = [];
3131

32-
/** @var array<string, mixed> */
32+
/** @var array<string|int, mixed> */
3333
private array $templates = [];
3434

3535
private ?string $name = null;
@@ -65,7 +65,7 @@ public function isValid(mixed $input): bool
6565
return $this->evaluate($input)->isValid;
6666
}
6767

68-
/** @param array<string, mixed>|callable(ValidationException): Throwable|string|Throwable|null $template */
68+
/** @param array<string|int, mixed>|callable(ValidationException): Throwable|string|Throwable|null $template */
6969
public function assert(mixed $input, array|string|Throwable|callable|null $template = null): void
7070
{
7171
$result = $this->evaluate($input);
@@ -99,7 +99,7 @@ public function assert(mixed $input, array|string|Throwable|callable|null $templ
9999
throw $template($exception);
100100
}
101101

102-
/** @param array<string, mixed> $templates */
102+
/** @param array<string|int, mixed> $templates */
103103
public function setTemplates(array $templates): self
104104
{
105105
$this->templates = $templates;

tests/feature/Issues/Issue1289Test.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
- description must be a string value
5555
FULL_MESSAGE,
5656
[
57+
'__root__' => 'Each item in `[["default": 2, "description": [], "children": ["nope"]]]` must be valid',
5758
0 => [
5859
'__root__' => 'These rules must pass for `["default": 2, "description": [], "children": ["nope"]]`',
5960
'default' => [

0 commit comments

Comments
 (0)