Skip to content

Commit 259be0c

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 259be0c

24 files changed

+235
-108
lines changed

library/Message/StandardFormatter.php

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function __construct(
3535
}
3636

3737
/**
38-
* @param array<string, mixed> $templates
38+
* @param array<string|int, mixed> $templates
3939
*/
4040
public function main(Result $result, array $templates, Translator $translator): string
4141
{
@@ -50,7 +50,7 @@ public function main(Result $result, array $templates, Translator $translator):
5050
}
5151

5252
/**
53-
* @param array<string, mixed> $templates
53+
* @param array<string|int, mixed> $templates
5454
*/
5555
public function full(
5656
Result $result,
@@ -91,43 +91,46 @@ public function full(
9191
}
9292

9393
/**
94-
* @param array<string, mixed> $templates
94+
* @param array<string|int, mixed> $templates
9595
*
96-
* @return array<string, mixed>
96+
* @return array<string|int, mixed>
9797
*/
9898
public function array(Result $result, array $templates, Translator $translator): array
9999
{
100100
$selectedTemplates = $this->selectTemplates($result, $templates);
101101
$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-
];
106-
}
107102

108-
$messages = [];
103+
$messages = [
104+
'__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator),
105+
];
106+
107+
$children = [];
108+
109109
foreach ($deduplicatedChildren as $child) {
110-
$messages[$child->id] = $this->array(
110+
$childKey = $child->path ?? $child->id;
111+
112+
$children[$childKey] = $this->array(
111113
$child,
112114
$this->selectTemplates($child, $selectedTemplates),
113115
$translator
114116
);
115-
if (count($messages[$child->id]) !== 1) {
117+
118+
if (count($children[$childKey]) !== 1) {
116119
continue;
117120
}
118121

119-
$messages[$child->id] = current($messages[$child->id]);
122+
$children[$childKey] = current($children[$childKey]);
120123
}
121124

122-
if (count($messages) > 1) {
123-
$self = [
124-
'__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator),
125-
];
125+
if (count($children) === 0 || $this->isFinalTemplate($result, $selectedTemplates)) {
126+
return [$result->path ?? $result->id => $messages['__root__']];
127+
}
126128

127-
return $self + $messages;
129+
if ($result->path !== null) {
130+
return [$result->path => $messages + $children];
128131
}
129132

130-
return $messages;
133+
return $messages + $children;
131134
}
132135

133136
private function isAlwaysVisible(Result $result, Result ...$siblings): bool
@@ -165,56 +168,69 @@ private function isAlwaysVisible(Result $result, Result ...$siblings): bool
165168
);
166169
}
167170

168-
/** @param array<string, mixed> $templates */
171+
/** @param array<string|int, mixed> $templates */
169172
private function getTemplated(Result $result, array $templates): Result
170173
{
171174
if ($result->hasCustomTemplate()) {
172175
return $result;
173176
}
174177

175-
if (!isset($templates[$result->id]) && isset($templates['__root__'])) {
176-
return $result->withTemplate($templates['__root__']);
178+
$keys = [$result->name, $result->path, $result->id, '__root__'];
179+
foreach ($keys as $key) {
180+
if (isset($templates[$key]) && is_string($templates[$key])) {
181+
return $result->withTemplate($templates[$key]);
182+
}
177183
}
178184

179-
if (!isset($templates[$result->id])) {
185+
if (!isset($templates[$result->id]) && !isset($templates[$result->path]) && !isset($templates[$result->name])) {
180186
return $result;
181187
}
182188

183-
$template = $templates[$result->id];
184-
if (is_string($template)) {
185-
return $result->withTemplate($template);
186-
}
187-
188189
throw new ComponentException(
189-
sprintf('Template for "%s" must be a string, %s given', $result->id, stringify($template))
190+
sprintf(
191+
'Template for "%s" must be a string, %s given',
192+
$result->path ?? $result->name ?? $result->id,
193+
stringify($templates)
194+
)
190195
);
191196
}
192197

193198
/**
194-
* @param array<string, mixed> $templates
199+
* @param array<string|int, mixed> $templates
195200
*/
196201
private function isFinalTemplate(Result $result, array $templates): bool
197202
{
198-
if (isset($templates[$result->id]) && is_string($templates[$result->id])) {
199-
return true;
203+
$keys = [$result->name, $result->path, $result->id];
204+
foreach ($keys as $key) {
205+
if (isset($templates[$key]) && is_string($templates[$key])) {
206+
return true;
207+
}
200208
}
201209

202210
if (count($templates) !== 1) {
203211
return false;
204212
}
205213

206-
return isset($templates['__root__']) || isset($templates[$result->id]);
214+
foreach ($keys as $key) {
215+
if (isset($templates[$key])) {
216+
return true;
217+
}
218+
}
219+
220+
return isset($templates['__root__']);
207221
}
208222

209223
/**
210-
* @param array<string, mixed> $templates
224+
* @param array<string|int, mixed> $templates
211225
*
212-
* @return array<string, mixed>
226+
* @return array<string|int, mixed>
213227
*/
214-
private function selectTemplates(Result $message, array $templates): array
228+
private function selectTemplates(Result $result, array $templates): array
215229
{
216-
if (isset($templates[$message->id]) && is_array($templates[$message->id])) {
217-
return $templates[$message->id];
230+
foreach ([$result->name, $result->path, $result->id] as $key) {
231+
if (isset($templates[$key]) && is_array($templates[$key])) {
232+
return $templates[$key];
233+
}
218234
}
219235

220236
return $templates;
@@ -227,7 +243,7 @@ private function extractDeduplicatedChildren(Result $result): array
227243
$deduplicatedResults = [];
228244
$duplicateCounters = [];
229245
foreach ($result->children as $child) {
230-
$id = $child->id;
246+
$id = $child->path ?? $child->id;
231247
if (isset($duplicateCounters[$id])) {
232248
$id .= '.' . ++$duplicateCounters[$id];
233249
} elseif (array_key_exists($id, $deduplicatedResults)) {
@@ -236,7 +252,7 @@ private function extractDeduplicatedChildren(Result $result): array
236252
$duplicateCounters[$id] = 2;
237253
$id .= '.2';
238254
}
239-
$deduplicatedResults[$id] = $child->isValid ? null : $child->withId($id);
255+
$deduplicatedResults[$id] = $child->isValid ? null : $child->withId((string) $id);
240256
}
241257

242258
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' => [

tests/feature/Issues/Issue1334Test.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,31 @@ function (): void {
3535
- street must be a string
3636
FULL_MESSAGE,
3737
[
38+
'__root__' => 'These rules must pass for `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]`',
3839
'each' => [
3940
'__root__' => 'Each item in `[["region": "Oregon", "country": "USA", "other": 123], ["street": "", "region": "Oregon", "country": "USA"], ["s ... ]` must be valid',
4041
0 => [
4142
'__root__' => 'These rules must pass for `["region": "Oregon", "country": "USA", "other": 123]`',
4243
'street' => 'street must be present',
43-
'other' => 'other must be a string or must be null',
44+
'other' => [
45+
'__root__' => 'These rules must pass for other',
46+
'nullOrStringType' => 'other must be a string or must be null',
47+
],
48+
],
49+
1 => [
50+
'__root__' => 'These rules must pass for `["street": "", "region": "Oregon", "country": "USA"]`',
51+
'street' => [
52+
'__root__' => 'These rules must pass for street',
53+
'notEmpty' => 'street must not be empty',
54+
],
55+
],
56+
2 => [
57+
'__root__' => 'These rules must pass for `["street": 123, "region": "Oregon", "country": "USA"]`',
58+
'street' => [
59+
'__root__' => 'These rules must pass for street',
60+
'stringType' => 'street must be a string',
61+
],
4462
],
45-
1 => 'street must not be empty',
46-
2 => 'street must be a string',
4763
],
4864
]
4965
));

0 commit comments

Comments
 (0)