Оглавление документации
English version
@modulify/validator возвращает структурированные violations вместо встроенных human-readable сообщений.
У этой части API два слоя:
- сырой результат
Violation[], который возвращаютvalidate(...)иvalidate.sync(...); ViolationCollection— тонкая utility-обёртка, создаваемая черезcollection(...).
import {
collection,
isString,
shape,
validate,
} from '@modulify/validator'
const [ok, validated, violations] = validate.sync({
profile: {
email: 42,
},
}, shape({
profile: shape({
email: isString,
}),
}))
const errors = collection(violations)
const emailErrors = errors.at(['profile', 'email'])Библиотека намеренно возвращает данные, а не presentation.
Это позволяет переиспользовать один и тот же результат для:
- локализованных UI-сообщений;
- form error state;
- API payloads;
- аналитики и диагностики;
- custom adapters и tooling.
Violation содержит:
value— значение, на котором произошла ошибка;path— место ошибки внутри вложенного объекта или массива;violates— machine-readable описание того, что именно нарушено.
Пример:
import type { Violation } from '@modulify/validator'
const violation: Violation = {
value: '',
path: ['form', 'nickname'],
violates: {
kind: 'assertion',
name: 'hasLength',
code: 'length.min',
args: [4],
},
}Поле violates содержит структурированную информацию о нарушении.
Важные части:
kind— какой слой произвёл ошибку;name— какая assertion или validator её создала;code— семантический код ошибки;args— структурированная нагрузка этой ошибки.
Это позволяет потребителю самому решать, как позже рендерить и трансформировать ошибки.
violates.kind показывает слой, который создал ошибку:
'assertion''validator''runtime'
Примеры:
- ошибки
isStringотносятся к assertion-level; shape.unknown-keyотносится к validator-level;- rejected async validations могут появиться как runtime-level ошибки.
path — это обычный PropertyKey[].
Это значит:
- свойства объекта остаются property keys;
- позиции массивов остаются числовыми индексами;
- вложенные ошибки сохраняют полный абсолютный путь от корня входного значения.
Примеры:
['profile', 'email']['items', 0, 'title']
Именно поэтому библиотека остаётся adapter-friendly и не сериализует пути в строки.
validate(...) и validate.sync(...) возвращают:
type ValidationTuple<T> =
| [ok: true, validated: T, violations: []]
| [ok: false, validated: unknown, violations: Violation[]]То есть:
- при успехе список violations пуст;
- при ошибке возвращается исходное значение и собранный список структурированных нарушений.
ViolationCollection — это тонкая convenience-обёртка над Violation[].
Она сохраняет модель raw list, но добавляет несколько операций для постобработки:
size- итерацию через
for...of .forEach(...).map(...).at(path).tree()
Её цель — удобство, а не вторая error model.
Используйте collection(...), чтобы обернуть сырой Violation[]:
const errors = collection(violations)Это особенно полезно, когда валидация уже произошла и дальше нужно:
- посмотреть ошибки конкретного поля;
- собрать вложенное UI-представление;
- сгруппировать или преобразовать коды;
- пользоваться helper-методами без изменения исходного формата данных.
.at(path) делает точное совпадение по пути.
const rootErrors = errors.at([])
const emailErrors = errors.at(['profile', 'email'])Важная семантика:
at([])означает root-level ошибки;- violations без
pathсчитаются root-level в collection utilities; at(['profile'])не включает['profile', 'email'];- результатом снова будет
ViolationCollection.
Это делает path lookup предсказуемым и простым.
.tree() строит вложенное machine-readable дерево из текущей коллекции.
const tree = errors.tree()Это полезно, если потребителю нужны:
- иерархический обход;
- рендеринг вложенных ошибок;
- path-aware UI state;
- структурированное debugging view.
Tree nodes содержат:
pathselfsubtreechildren.at(path)
Tree view имеет такой вид:
type ViolationTreeNode = {
path: readonly PropertyKey[]
self: ViolationCollection
subtree: ViolationCollection
children: ReadonlyMap<PropertyKey, ViolationTreeNode>
at(path: readonly PropertyKey[]): ViolationTreeNode | undefined
}Важно различать:
selfсодержит только violations ровно на текущем пути;subtreeсодержит нарушения на текущем пути и все нарушения потомков.
Tree nodes сохраняют абсолютные пути.
Кроме того:
- промежуточные узлы могут существовать, даже если у них нет собственных violations;
- они всё равно полезны, потому что у потомков могут быть ошибки;
- дерево строится из path arrays, а не из dot-separated строк.
Так структура остаётся согласованной с raw violation format.
const [ok, validated, violations] = validate.sync({
profile: {
email: '',
},
}, shape({
profile: shape({
email: [isString],
}),
}))
const errors = collection(violations)
const rootErrors = errors.at([])
const emailErrors = errors.at(['profile', 'email'])
const codes = emailErrors.map(violation => violation.violates.code)
const tree = errors.tree()Из одного raw списка violations можно получить:
- collections по точному пути;
- отображение кодов;
- дерево для вложенного обхода.
- сохраняйте
Violation[]как канонический transport format; - используйте
collection(...)только когда helper API действительно упрощает работу; - считайте
codeиargsглавными точками интеграции; - оставляйте message rendering вне validation layer;
- предпочитайте работу с path arrays вместо сериализации путей в строки.