Skip to content

Commit 6a39fad

Browse files
committed
WIP: Do not write messages when formatting result as an array
I've created a path in the result, that way some rules can change the path, not the ID.
1 parent 0e40917 commit 6a39fad

25 files changed

+302
-112
lines changed

library/Message/StandardFormatter.php

Lines changed: 54 additions & 44 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,41 @@ 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);
101-
$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-
}
101+
$messages = [
102+
'__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator),
103+
];
104+
105+
$children = [];
106+
foreach ($this->extractDeduplicatedChildren($result) as $child) {
107+
$childKey = $child->path ?? $child->id;
107108

108-
$messages = [];
109-
foreach ($deduplicatedChildren as $child) {
110-
$messages[$child->id] = $this->array(
109+
$children[$childKey] = $this->array(
111110
$child,
112111
$this->selectTemplates($child, $selectedTemplates),
113112
$translator
114113
);
115-
if (count($messages[$child->id]) !== 1) {
116-
continue;
117-
}
118114

119-
$messages[$child->id] = current($messages[$child->id]);
115+
if (count($children[$childKey]) === 1) {
116+
$children[$childKey] = current($children[$childKey]);
117+
}
120118
}
121119

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

127-
return $self + $messages;
124+
if ($result->path !== null) {
125+
return [$result->path => $messages + $children];
128126
}
129127

130-
return $messages;
128+
return $messages + $children;
131129
}
132130

133131
private function isAlwaysVisible(Result $result, Result ...$siblings): bool
@@ -165,56 +163,68 @@ private function isAlwaysVisible(Result $result, Result ...$siblings): bool
165163
);
166164
}
167165

168-
/** @param array<string, mixed> $templates */
166+
/** @param array<string|int, mixed> $templates */
169167
private function getTemplated(Result $result, array $templates): Result
170168
{
171169
if ($result->hasCustomTemplate()) {
172170
return $result;
173171
}
174172

175-
if (!isset($templates[$result->id]) && isset($templates['__root__'])) {
176-
return $result->withTemplate($templates['__root__']);
173+
foreach ([$result->path, $result->name, $result->id, '__root__'] as $key) {
174+
if (isset($templates[$key]) && is_string($templates[$key])) {
175+
return $result->withTemplate($templates[$key]);
176+
}
177177
}
178178

179-
if (!isset($templates[$result->id])) {
179+
if (!isset($templates[$result->id]) && !isset($templates[$result->path]) && !isset($templates[$result->name])) {
180180
return $result;
181181
}
182182

183-
$template = $templates[$result->id];
184-
if (is_string($template)) {
185-
return $result->withTemplate($template);
186-
}
187-
188183
throw new ComponentException(
189-
sprintf('Template for "%s" must be a string, %s given', $result->id, stringify($template))
184+
sprintf(
185+
'Template for "%s" must be a string, %s given',
186+
$result->path ?? $result->name ?? $result->id,
187+
stringify($templates)
188+
)
190189
);
191190
}
192191

193192
/**
194-
* @param array<string, mixed> $templates
193+
* @param array<string|int, mixed> $templates
195194
*/
196195
private function isFinalTemplate(Result $result, array $templates): bool
197196
{
198-
if (isset($templates[$result->id]) && is_string($templates[$result->id])) {
199-
return true;
197+
$keys = [$result->path, $result->name, $result->id];
198+
foreach ($keys as $key) {
199+
if (isset($templates[$key]) && is_string($templates[$key])) {
200+
return true;
201+
}
200202
}
201203

202204
if (count($templates) !== 1) {
203205
return false;
204206
}
205207

206-
return isset($templates['__root__']) || isset($templates[$result->id]);
208+
foreach ($keys as $key) {
209+
if (isset($templates[$key])) {
210+
return true;
211+
}
212+
}
213+
214+
return isset($templates['__root__']);
207215
}
208216

209217
/**
210-
* @param array<string, mixed> $templates
218+
* @param array<string|int, mixed> $templates
211219
*
212-
* @return array<string, mixed>
220+
* @return array<string|int, mixed>
213221
*/
214-
private function selectTemplates(Result $message, array $templates): array
222+
private function selectTemplates(Result $result, array $templates): array
215223
{
216-
if (isset($templates[$message->id]) && is_array($templates[$message->id])) {
217-
return $templates[$message->id];
224+
foreach ([$result->path, $result->name, $result->id] as $key) {
225+
if (isset($templates[$key]) && is_array($templates[$key])) {
226+
return $templates[$key];
227+
}
218228
}
219229

220230
return $templates;
@@ -227,7 +237,7 @@ private function extractDeduplicatedChildren(Result $result): array
227237
$deduplicatedResults = [];
228238
$duplicateCounters = [];
229239
foreach ($result->children as $child) {
230-
$id = $child->id;
240+
$id = $child->path ?? $child->id;
231241
if (isset($duplicateCounters[$id])) {
232242
$id .= '.' . ++$duplicateCounters[$id];
233243
} elseif (array_key_exists($id, $deduplicatedResults)) {
@@ -236,7 +246,7 @@ private function extractDeduplicatedChildren(Result $result): array
236246
$duplicateCounters[$id] = 2;
237247
$id .= '.2';
238248
}
239-
$deduplicatedResults[$id] = $child->isValid ? null : $child->withId($id);
249+
$deduplicatedResults[$id] = $child->isValid ? null : $child->withId((string) $id);
240250
}
241251

242252
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)