@modulify/validator 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +324 -107
- package/dist/assert.cjs +66 -0
- package/dist/assert.d.ts +16 -0
- package/dist/assert.mjs +66 -0
- package/dist/assertions.cjs +190 -92
- package/dist/assertions.d.ts +58 -2
- package/dist/assertions.mjs +191 -93
- package/dist/checkers.d.ts +8 -0
- package/dist/combinators.cjs +341 -0
- package/dist/combinators.d.ts +17 -0
- package/dist/combinators.mjs +341 -0
- package/dist/constraints.d.ts +4 -0
- package/dist/extractors.d.ts +2 -0
- package/dist/index.cjs +172 -61
- package/dist/index.d.ts +10 -4
- package/dist/index.mjs +176 -64
- package/dist/json-schema.cjs +514 -0
- package/dist/json-schema.d.ts +14 -0
- package/dist/json-schema.mjs +514 -0
- package/dist/metadata.cjs +8 -0
- package/dist/metadata.cjs.js +130 -0
- package/dist/metadata.d.ts +8 -0
- package/dist/metadata.es.js +131 -0
- package/dist/metadata.mjs +8 -0
- package/dist/predicates.cjs +40 -5
- package/dist/predicates.d.ts +25 -3
- package/dist/predicates.mjs +40 -5
- package/dist/violations.d.ts +29 -0
- package/docs/en/00-index.md +14 -0
- package/docs/en/01-shape-api.md +348 -0
- package/docs/en/02-metadata-and-introspection.md +276 -0
- package/docs/en/03-violations.md +267 -0
- package/docs/en/04-json-schema-export.md +264 -0
- package/docs/en/05-public-api.md +123 -0
- package/docs/en/06-common-recipes.md +273 -0
- package/docs/en/07-ai-reference.md +215 -0
- package/docs/en/08-violation-code-types.md +241 -0
- package/docs/ru/00-index.md +15 -0
- package/docs/ru/01-shape-api.md +348 -0
- package/docs/ru/02-metadata-and-introspection.md +276 -0
- package/docs/ru/03-violations.md +267 -0
- package/docs/ru/04-json-schema-export.md +264 -0
- package/docs/ru/05-public-api.md +123 -0
- package/docs/ru/06-common-recipes.md +273 -0
- package/docs/ru/07-ai-reference.md +215 -0
- package/docs/ru/08-violation-code-types.md +241 -0
- package/docs/ru/README.md +371 -0
- package/package.json +51 -33
- package/types/index.d.ts +789 -30
- package/types/json-schema.d.ts +75 -0
- package/dist/assertions/Assert.d.ts +0 -2
- package/dist/assertions/HasLength.d.ts +0 -7
- package/dist/assertions/check.d.ts +0 -3
- package/dist/assertions/index.d.ts +0 -16
- package/dist/runners/Each.d.ts +0 -3
- package/dist/runners/HasProperties.d.ts +0 -6
- package/dist/runners/index.d.ts +0 -2
- package/dist/runners.cjs +0 -32
- package/dist/runners.d.ts +0 -2
- package/dist/runners.mjs +0 -32
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Метаданные и интроспекция
|
|
2
|
+
|
|
3
|
+
[Оглавление документации](./00-index.md)
|
|
4
|
+
[English version](../en/02-metadata-and-introspection.md)
|
|
5
|
+
|
|
6
|
+
`@modulify/validator` предоставляет публичный слой introspection, построенный вокруг двух небольших entrypoint-ов:
|
|
7
|
+
|
|
8
|
+
- `meta(...)` для привязки machine-readable metadata к любому constraint;
|
|
9
|
+
- `describe(...)` для чтения стабильного рекурсивного descriptor tree из built-in constraints и совместимых custom validators.
|
|
10
|
+
|
|
11
|
+
Этот слой намеренно adapter-oriented. Он нужен для того, чтобы прикладной код и tooling могли исследовать структуру валидации без зависимости от приватных runtime internals.
|
|
12
|
+
|
|
13
|
+
## Быстрый старт
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import {
|
|
17
|
+
describe,
|
|
18
|
+
isString,
|
|
19
|
+
meta,
|
|
20
|
+
optional,
|
|
21
|
+
shape,
|
|
22
|
+
} from '@modulify/validator'
|
|
23
|
+
|
|
24
|
+
const registration = meta(shape({
|
|
25
|
+
email: meta(isString, {
|
|
26
|
+
title: 'Email',
|
|
27
|
+
format: 'email',
|
|
28
|
+
}),
|
|
29
|
+
nickname: optional(isString),
|
|
30
|
+
}).strict(), {
|
|
31
|
+
title: 'Registration form',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const descriptor = describe(registration)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Что делает `meta(...)`
|
|
38
|
+
|
|
39
|
+
`meta(...)` добавляет read-only machine-readable metadata к constraint, не меняя validation semantics.
|
|
40
|
+
|
|
41
|
+
Это значит:
|
|
42
|
+
|
|
43
|
+
- исходный constraint не мутируется;
|
|
44
|
+
- поведение валидации при успехе и ошибке остаётся тем же;
|
|
45
|
+
- metadata становится видна через `describe(...)`;
|
|
46
|
+
- вложенная metadata остаётся ровно там, где была применена.
|
|
47
|
+
|
|
48
|
+
Пример:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const email = meta(isString, {
|
|
52
|
+
title: 'Email',
|
|
53
|
+
widget: 'email',
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Тот же механизм работает для:
|
|
58
|
+
|
|
59
|
+
- assertions;
|
|
60
|
+
- wrappers вроде `optional(...)`;
|
|
61
|
+
- object shapes;
|
|
62
|
+
- structural validators вроде `each(...)` или `tuple(...)`;
|
|
63
|
+
- custom validators.
|
|
64
|
+
|
|
65
|
+
## Metadata явная, а не наследуемая
|
|
66
|
+
|
|
67
|
+
Metadata не распространяется автоматически по descriptor tree.
|
|
68
|
+
|
|
69
|
+
Если metadata повешена на parent shape, она остаётся на узле shape. Если metadata повешена на child field, она остаётся на узле этого поля.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const profile = meta(shape({
|
|
73
|
+
email: meta(isString, { title: 'Email' }),
|
|
74
|
+
name: isString,
|
|
75
|
+
}), {
|
|
76
|
+
title: 'Profile',
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
В этом примере:
|
|
81
|
+
|
|
82
|
+
- узел shape получает `title: 'Profile'`;
|
|
83
|
+
- поле `email` получает `title: 'Email'`;
|
|
84
|
+
- поле `name` не получает metadata, пока вы не добавите её явно.
|
|
85
|
+
|
|
86
|
+
Это делает metadata предсказуемой и убирает скрытое tree-wide поведение.
|
|
87
|
+
|
|
88
|
+
## Объединение metadata
|
|
89
|
+
|
|
90
|
+
Если `meta(...)` применяется к одному и тому же constraint несколько раз, metadata merge-ится слева направо.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const annotated = meta(
|
|
94
|
+
meta(isString, { title: 'Email' }),
|
|
95
|
+
{ placeholder: 'name@example.com' }
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
В результате получится один descriptor node, у которого `metadata` содержит оба ключа.
|
|
100
|
+
|
|
101
|
+
## Что возвращает `describe(...)`
|
|
102
|
+
|
|
103
|
+
`describe(...)` возвращает стабильный рекурсивный descriptor tree.
|
|
104
|
+
|
|
105
|
+
Descriptor намеренно machine-readable, а не presentation-oriented. Он спроектирован для adapters и tooling, а не для встроенного рендеринга human-readable messages.
|
|
106
|
+
|
|
107
|
+
Примеры built-in `kind`:
|
|
108
|
+
|
|
109
|
+
- `'assertion'`;
|
|
110
|
+
- `'allOf'`;
|
|
111
|
+
- `'optional'`, `'nullable'`, `'nullish'`;
|
|
112
|
+
- `'shape'`, `'each'`, `'tuple'`, `'record'`;
|
|
113
|
+
- `'union'`, `'discriminatedUnion'`;
|
|
114
|
+
- `'validator'` как generic fallback для custom validators без публичного structural descriptor.
|
|
115
|
+
|
|
116
|
+
## Descriptor-ы assertions
|
|
117
|
+
|
|
118
|
+
Leaf assertions создают descriptors с:
|
|
119
|
+
|
|
120
|
+
- `kind: 'assertion'`;
|
|
121
|
+
- `name`;
|
|
122
|
+
- `bail`;
|
|
123
|
+
- основными `code` и `args`;
|
|
124
|
+
- `constraints` для дополнительного checker pipeline;
|
|
125
|
+
- опциональной `metadata`.
|
|
126
|
+
|
|
127
|
+
Пример:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const descriptor = describe(meta(isString, { title: 'Display name' }))
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Так assertion metadata становится доступной без изменения runtime-поведения самой assertion.
|
|
134
|
+
|
|
135
|
+
## Descriptor-ы wrappers и structural validators
|
|
136
|
+
|
|
137
|
+
Composed validators описывают себя рекурсивно.
|
|
138
|
+
|
|
139
|
+
Примеры:
|
|
140
|
+
|
|
141
|
+
- `optional(...)` раскрывает `child`;
|
|
142
|
+
- `each(...)` раскрывает `item`;
|
|
143
|
+
- `tuple(...)` раскрывает `items`;
|
|
144
|
+
- `union(...)` раскрывает `branches`;
|
|
145
|
+
- `record(...)` раскрывает `values`.
|
|
146
|
+
|
|
147
|
+
Именно эта рекурсивная структура делает возможными adapter layers без доступа к runtime internals валидаторов.
|
|
148
|
+
|
|
149
|
+
## Descriptor-ы shape
|
|
150
|
+
|
|
151
|
+
У shape один из самых богатых built-in descriptor nodes.
|
|
152
|
+
|
|
153
|
+
Shape descriptor включает:
|
|
154
|
+
|
|
155
|
+
- `metadata`;
|
|
156
|
+
- `unknownKeys` со значениями `'passthrough'` или `'strict'`;
|
|
157
|
+
- `fields` с descriptor-ами полей;
|
|
158
|
+
- `rules` с компактными summary object-level rules вроде `refine(...)` и `fieldsMatch(...)`.
|
|
159
|
+
|
|
160
|
+
Пример:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const descriptor = describe(shape({
|
|
164
|
+
email: meta(isString, { format: 'email' }),
|
|
165
|
+
password: isString,
|
|
166
|
+
}).strict())
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Это особенно полезно для:
|
|
170
|
+
|
|
171
|
+
- form adapters;
|
|
172
|
+
- contract adapters;
|
|
173
|
+
- custom documentation generators;
|
|
174
|
+
- JSON Schema export через отдельный derivation layer.
|
|
175
|
+
|
|
176
|
+
## Правила уровня объекта в introspection
|
|
177
|
+
|
|
178
|
+
Сами callbacks из `refine(...)` не сериализуются.
|
|
179
|
+
|
|
180
|
+
Вместо этого shape хранит лёгкие rule descriptors в `rules`.
|
|
181
|
+
|
|
182
|
+
Built-in примеры:
|
|
183
|
+
|
|
184
|
+
- `fieldsMatch(...)` создаёт компактный `fieldsMatch` rule descriptor;
|
|
185
|
+
- `refine(...)` может принимать собственный компактный rule descriptor object.
|
|
186
|
+
|
|
187
|
+
Так публичное descriptor tree остаётся стабильным и достаточно сериализуемым для tooling, при этом библиотека не пытается сериализовать произвольные callbacks.
|
|
188
|
+
|
|
189
|
+
## Пользовательские validators
|
|
190
|
+
|
|
191
|
+
Custom validators могут участвовать в том же слое introspection через `custom(...)` и публичный метод `describe()`.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { custom } from '@modulify/validator'
|
|
195
|
+
|
|
196
|
+
const isoDate = custom({
|
|
197
|
+
check(value: unknown): value is string {
|
|
198
|
+
return typeof value === 'string'
|
|
199
|
+
},
|
|
200
|
+
run() {
|
|
201
|
+
return []
|
|
202
|
+
},
|
|
203
|
+
describe() {
|
|
204
|
+
return {
|
|
205
|
+
kind: 'stringFormat',
|
|
206
|
+
format: 'iso-date',
|
|
207
|
+
} as const
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Тогда:
|
|
213
|
+
|
|
214
|
+
- `describe(isoDate)` вернёт этот публичный descriptor;
|
|
215
|
+
- `meta(...)` всё ещё сможет добавить metadata поверх него.
|
|
216
|
+
|
|
217
|
+
Если custom validator не реализует `describe()`, `describe(...)` вернёт fallback:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
{ kind: 'validator' }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Этот fallback специально минимальный.
|
|
224
|
+
|
|
225
|
+
## Пример adapter-а
|
|
226
|
+
|
|
227
|
+
Небольшой adapter может пройти по shape descriptor и собрать widget hints:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import type { ConstraintDescriptor } from '@modulify/validator'
|
|
231
|
+
|
|
232
|
+
function collectFieldWidgets(node: ConstraintDescriptor) {
|
|
233
|
+
if (node.kind !== 'shape') {
|
|
234
|
+
return {}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return Object.fromEntries(
|
|
238
|
+
Object.entries(node.fields).map(([key, child]) => [key, child.metadata?.widget ?? 'text'])
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
В этом и состоит основная цель слоя introspection: внешний код может построить своё представление, не зная, как именно внутри реализована runtime validation.
|
|
244
|
+
|
|
245
|
+
## Связь с экспортом JSON Schema
|
|
246
|
+
|
|
247
|
+
`toJsonSchema(...)` — это отдельный слой, производный от публичного descriptor contract.
|
|
248
|
+
|
|
249
|
+
Это значит:
|
|
250
|
+
|
|
251
|
+
- `describe(...)` — публичный источник истины для introspection;
|
|
252
|
+
- JSON Schema export строится поверх него;
|
|
253
|
+
- библиотека не поддерживает вторую скрытую schema model специально для exporter-ов.
|
|
254
|
+
|
|
255
|
+
Такое разделение сделано намеренно:
|
|
256
|
+
|
|
257
|
+
- runtime validation остаётся runtime-first;
|
|
258
|
+
- metadata остаётся явной;
|
|
259
|
+
- export behavior может развиваться как тонкий adapter layer.
|
|
260
|
+
|
|
261
|
+
## Практические границы
|
|
262
|
+
|
|
263
|
+
Текущие границы слоя metadata и introspection:
|
|
264
|
+
|
|
265
|
+
- нет встроенного message rendering или i18n;
|
|
266
|
+
- нет metadata inheritance по дереву;
|
|
267
|
+
- нет отдельного schema DSL параллельно runtime constraints;
|
|
268
|
+
- нет сериализации runtime callbacks из `refine(...)`;
|
|
269
|
+
- custom validators без публичных descriptors намеренно остаются opaque.
|
|
270
|
+
|
|
271
|
+
## Практические замечания
|
|
272
|
+
|
|
273
|
+
- используйте `meta(...)`, когда нужны machine-readable annotations, а не скрытое поведение;
|
|
274
|
+
- используйте `describe(...)`, когда нужен стабильный structural view дерева constraint-ов;
|
|
275
|
+
- держите custom descriptors компактными и ecosystem-facing;
|
|
276
|
+
- внешние форматы лучше получать derivation-слоями поверх descriptors, а не чтением приватных validator internals.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Нарушения
|
|
2
|
+
|
|
3
|
+
[Оглавление документации](./00-index.md)
|
|
4
|
+
[English version](../en/03-violations.md)
|
|
5
|
+
|
|
6
|
+
`@modulify/validator` возвращает структурированные violations вместо встроенных human-readable сообщений.
|
|
7
|
+
|
|
8
|
+
У этой части API два слоя:
|
|
9
|
+
|
|
10
|
+
- сырой результат `Violation[]`, который возвращают `validate(...)` и `validate.sync(...)`;
|
|
11
|
+
- `ViolationCollection` — тонкая utility-обёртка, создаваемая через `collection(...)`.
|
|
12
|
+
|
|
13
|
+
## Быстрый старт
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import {
|
|
17
|
+
collection,
|
|
18
|
+
isString,
|
|
19
|
+
shape,
|
|
20
|
+
validate,
|
|
21
|
+
} from '@modulify/validator'
|
|
22
|
+
|
|
23
|
+
const [ok, validated, violations] = validate.sync({
|
|
24
|
+
profile: {
|
|
25
|
+
email: 42,
|
|
26
|
+
},
|
|
27
|
+
}, shape({
|
|
28
|
+
profile: shape({
|
|
29
|
+
email: isString,
|
|
30
|
+
}),
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
const errors = collection(violations)
|
|
34
|
+
const emailErrors = errors.at(['profile', 'email'])
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Почему нарушения структурированы
|
|
38
|
+
|
|
39
|
+
Библиотека намеренно возвращает данные, а не presentation.
|
|
40
|
+
|
|
41
|
+
Это позволяет переиспользовать один и тот же результат для:
|
|
42
|
+
|
|
43
|
+
- локализованных UI-сообщений;
|
|
44
|
+
- form error state;
|
|
45
|
+
- API payloads;
|
|
46
|
+
- аналитики и диагностики;
|
|
47
|
+
- custom adapters и tooling.
|
|
48
|
+
|
|
49
|
+
## Формат `Violation`
|
|
50
|
+
|
|
51
|
+
Violation содержит:
|
|
52
|
+
|
|
53
|
+
- `value` — значение, на котором произошла ошибка;
|
|
54
|
+
- `path` — место ошибки внутри вложенного объекта или массива;
|
|
55
|
+
- `violates` — machine-readable описание того, что именно нарушено.
|
|
56
|
+
|
|
57
|
+
Пример:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import type { Violation } from '@modulify/validator'
|
|
61
|
+
|
|
62
|
+
const violation: Violation = {
|
|
63
|
+
value: '',
|
|
64
|
+
path: ['form', 'nickname'],
|
|
65
|
+
violates: {
|
|
66
|
+
kind: 'assertion',
|
|
67
|
+
name: 'hasLength',
|
|
68
|
+
code: 'length.min',
|
|
69
|
+
args: [4],
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## `violates`
|
|
75
|
+
|
|
76
|
+
Поле `violates` содержит структурированную информацию о нарушении.
|
|
77
|
+
|
|
78
|
+
Важные части:
|
|
79
|
+
|
|
80
|
+
- `kind` — какой слой произвёл ошибку;
|
|
81
|
+
- `name` — какая assertion или validator её создала;
|
|
82
|
+
- `code` — семантический код ошибки;
|
|
83
|
+
- `args` — структурированная нагрузка этой ошибки.
|
|
84
|
+
|
|
85
|
+
Это позволяет потребителю самому решать, как позже рендерить и трансформировать ошибки.
|
|
86
|
+
|
|
87
|
+
## `violates.kind`
|
|
88
|
+
|
|
89
|
+
`violates.kind` показывает слой, который создал ошибку:
|
|
90
|
+
|
|
91
|
+
- `'assertion'`
|
|
92
|
+
- `'validator'`
|
|
93
|
+
- `'runtime'`
|
|
94
|
+
|
|
95
|
+
Примеры:
|
|
96
|
+
|
|
97
|
+
- ошибки `isString` относятся к assertion-level;
|
|
98
|
+
- `shape.unknown-key` относится к validator-level;
|
|
99
|
+
- rejected async validations могут появиться как runtime-level ошибки.
|
|
100
|
+
|
|
101
|
+
## Вложенные пути
|
|
102
|
+
|
|
103
|
+
`path` — это обычный `PropertyKey[]`.
|
|
104
|
+
|
|
105
|
+
Это значит:
|
|
106
|
+
|
|
107
|
+
- свойства объекта остаются property keys;
|
|
108
|
+
- позиции массивов остаются числовыми индексами;
|
|
109
|
+
- вложенные ошибки сохраняют полный абсолютный путь от корня входного значения.
|
|
110
|
+
|
|
111
|
+
Примеры:
|
|
112
|
+
|
|
113
|
+
- `['profile', 'email']`
|
|
114
|
+
- `['items', 0, 'title']`
|
|
115
|
+
|
|
116
|
+
Именно поэтому библиотека остаётся adapter-friendly и не сериализует пути в строки.
|
|
117
|
+
|
|
118
|
+
## Результаты валидации
|
|
119
|
+
|
|
120
|
+
`validate(...)` и `validate.sync(...)` возвращают:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
type ValidationTuple<T> =
|
|
124
|
+
| [ok: true, validated: T, violations: []]
|
|
125
|
+
| [ok: false, validated: unknown, violations: Violation[]]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
То есть:
|
|
129
|
+
|
|
130
|
+
- при успехе список violations пуст;
|
|
131
|
+
- при ошибке возвращается исходное значение и собранный список структурированных нарушений.
|
|
132
|
+
|
|
133
|
+
## `ViolationCollection`
|
|
134
|
+
|
|
135
|
+
`ViolationCollection` — это тонкая convenience-обёртка над `Violation[]`.
|
|
136
|
+
|
|
137
|
+
Она сохраняет модель raw list, но добавляет несколько операций для постобработки:
|
|
138
|
+
|
|
139
|
+
- `size`
|
|
140
|
+
- итерацию через `for...of`
|
|
141
|
+
- `.forEach(...)`
|
|
142
|
+
- `.map(...)`
|
|
143
|
+
- `.at(path)`
|
|
144
|
+
- `.tree()`
|
|
145
|
+
|
|
146
|
+
Её цель — удобство, а не вторая error model.
|
|
147
|
+
|
|
148
|
+
## `collection(...)`
|
|
149
|
+
|
|
150
|
+
Используйте `collection(...)`, чтобы обернуть сырой `Violation[]`:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const errors = collection(violations)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Это особенно полезно, когда валидация уже произошла и дальше нужно:
|
|
157
|
+
|
|
158
|
+
- посмотреть ошибки конкретного поля;
|
|
159
|
+
- собрать вложенное UI-представление;
|
|
160
|
+
- сгруппировать или преобразовать коды;
|
|
161
|
+
- пользоваться helper-методами без изменения исходного формата данных.
|
|
162
|
+
|
|
163
|
+
## Точный поиск по пути через `.at(path)`
|
|
164
|
+
|
|
165
|
+
`.at(path)` делает точное совпадение по пути.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const rootErrors = errors.at([])
|
|
169
|
+
const emailErrors = errors.at(['profile', 'email'])
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Важная семантика:
|
|
173
|
+
|
|
174
|
+
- `at([])` означает root-level ошибки;
|
|
175
|
+
- violations без `path` считаются root-level в collection utilities;
|
|
176
|
+
- `at(['profile'])` не включает `['profile', 'email']`;
|
|
177
|
+
- результатом снова будет `ViolationCollection`.
|
|
178
|
+
|
|
179
|
+
Это делает path lookup предсказуемым и простым.
|
|
180
|
+
|
|
181
|
+
## Древовидное представление через `.tree()`
|
|
182
|
+
|
|
183
|
+
`.tree()` строит вложенное machine-readable дерево из текущей коллекции.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const tree = errors.tree()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Это полезно, если потребителю нужны:
|
|
190
|
+
|
|
191
|
+
- иерархический обход;
|
|
192
|
+
- рендеринг вложенных ошибок;
|
|
193
|
+
- path-aware UI state;
|
|
194
|
+
- структурированное debugging view.
|
|
195
|
+
|
|
196
|
+
Tree nodes содержат:
|
|
197
|
+
|
|
198
|
+
- `path`
|
|
199
|
+
- `self`
|
|
200
|
+
- `subtree`
|
|
201
|
+
- `children`
|
|
202
|
+
- `.at(path)`
|
|
203
|
+
|
|
204
|
+
## `ViolationTreeNode`
|
|
205
|
+
|
|
206
|
+
Tree view имеет такой вид:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
type ViolationTreeNode = {
|
|
210
|
+
path: readonly PropertyKey[]
|
|
211
|
+
self: ViolationCollection
|
|
212
|
+
subtree: ViolationCollection
|
|
213
|
+
children: ReadonlyMap<PropertyKey, ViolationTreeNode>
|
|
214
|
+
at(path: readonly PropertyKey[]): ViolationTreeNode | undefined
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Важно различать:
|
|
219
|
+
|
|
220
|
+
- `self` содержит только violations ровно на текущем пути;
|
|
221
|
+
- `subtree` содержит нарушения на текущем пути и все нарушения потомков.
|
|
222
|
+
|
|
223
|
+
## Семантика путей в дереве
|
|
224
|
+
|
|
225
|
+
Tree nodes сохраняют абсолютные пути.
|
|
226
|
+
|
|
227
|
+
Кроме того:
|
|
228
|
+
|
|
229
|
+
- промежуточные узлы могут существовать, даже если у них нет собственных violations;
|
|
230
|
+
- они всё равно полезны, потому что у потомков могут быть ошибки;
|
|
231
|
+
- дерево строится из path arrays, а не из dot-separated строк.
|
|
232
|
+
|
|
233
|
+
Так структура остаётся согласованной с raw violation format.
|
|
234
|
+
|
|
235
|
+
## Практический пример
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const [ok, validated, violations] = validate.sync({
|
|
239
|
+
profile: {
|
|
240
|
+
email: '',
|
|
241
|
+
},
|
|
242
|
+
}, shape({
|
|
243
|
+
profile: shape({
|
|
244
|
+
email: [isString],
|
|
245
|
+
}),
|
|
246
|
+
}))
|
|
247
|
+
|
|
248
|
+
const errors = collection(violations)
|
|
249
|
+
const rootErrors = errors.at([])
|
|
250
|
+
const emailErrors = errors.at(['profile', 'email'])
|
|
251
|
+
const codes = emailErrors.map(violation => violation.violates.code)
|
|
252
|
+
const tree = errors.tree()
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Из одного raw списка violations можно получить:
|
|
256
|
+
|
|
257
|
+
- collections по точному пути;
|
|
258
|
+
- отображение кодов;
|
|
259
|
+
- дерево для вложенного обхода.
|
|
260
|
+
|
|
261
|
+
## Практические замечания
|
|
262
|
+
|
|
263
|
+
- сохраняйте `Violation[]` как канонический transport format;
|
|
264
|
+
- используйте `collection(...)` только когда helper API действительно упрощает работу;
|
|
265
|
+
- считайте `code` и `args` главными точками интеграции;
|
|
266
|
+
- оставляйте message rendering вне validation layer;
|
|
267
|
+
- предпочитайте работу с path arrays вместо сериализации путей в строки.
|