validator v1.1
Ещё один парсер-валидатор, заточенный под обработку входных параметров JSON API. Радикальная переделка и упрощение
версии 1.0.
Подача на вход строки или потока, содержащего JSON. Поучение на выходе any с разобранными данными и формализованного
списка ошибок валидации.
N.B. Да, не struct. Но, ИМХО, для построения SQL из заданных в параметрах запросов фильтров map[string]any
удобнее.
Контроль типов полей. Nullable-поля, обязательные поля, значения по умолчанию для несуществующих полей и/или полей со
значением null. Регистрация ошибок появления в запросе полей имена которых неизвестны валидатору.
Возможность добавления собственных правил валидации (действий) и собственных валидируемых типов данных.
Не реализованы возможности сравнения значений двух полей (пароль и повтор пароля), проверки обязательность одного (
любого) поля в группе полей и прочих зависимостей входных данных. Большая часть подобных правил может быть легко
добавлена, но лично мне они пока что без надобности.
Чтобы были понятны задачи, решаемые валидатором, привожу типовое тело запроса (имена полей взяты с потолка, но
структура данных реальна и используется в работающих web-api):
{
"page": {
"page": 2,
"size": 50
},
"fields": [
"id",
"created",
"age",
"city"
],
"orders": [
{
"field": "age",
"order": "desc"
}
],
"filters": {
"city": {
"in": [
"Бийск",
"Барнаул"
]
},
"age": {
">=": 18,
"<=": 30
}
}
}
Пагинатор, список возвращаемых полей, сортировки, фильтры... Любые из секций могут отсутствовать, вариантов фильтров
куда больше.
Зачем?
Накатило... По работе пришлось более плотно заняться сервисами на Go. Так что посмотрел пару популярных валидаторов на
аннотациях, код своего предшественника, вернулся к своему говнокоду тех времён, когда механизмов обобщённого
программирования в Go ещё не было, сравнил с тем, что сам же делал на PHP, и захотелось сделать очередной велосипед - с
блек-джеком и... В общем, приступ графомании, с которым проще не бороться, а написать очередной велосипед - в свободное
от основной работы время.
А если серьёзно, то мне совершенно не нравятся малопригодные для возврата из API сообщения об ошибках (впрочем,
некоторые валидаторы позволяют их настраивать), невозможность отслеживать в запросах лишние поля, неизвестные
валидатору, и крайне хрупкие аннотации в Go. Отладка аннотаций - даже для чего-то простого - геморрой. А уж если у нас
API с развесистым набором опциональных параметров...
Хотелось получить достаточно надёжную более-менее легко тестируемую маленькую и легко расширяемую систему, имеющую
приемлемый объём описания валидатора в пользовательском коде. В которой вся предварительная работа по созданию и
настройке валидатора вынесена в инициализацию запускаемого приложения. Без использования автогенерации Go-кода, которая
мне неинтересна. C максимальным использованием типизации и минимальным использованием рефлексии в процессе валидации.
Без хранения состояния внутри валидатора (беспроблемное использование в параллельных процессах без необходимости
блокировок).
Скорость инициализации не имеет значения, т.к. валидатор ориентирован на web-сервисы, работающие 24/7.
Принцип работы
Никаких struct и аннотаций.
JSON декодируется в any, содержащий значения nil, string, bool, float64, []any, map[string]any (штатный
декодер JSON языка Go), после чего валидатор анализирует эти сырые данные и возвращает any, содержащий значения
целевых типов, и регистратор ошибок, содержащий информацию об ошибках входных данных в удобном для возврата из API виде.
N.B. В JavaScript не существует привычных целых чисел: для эмуляции int32 используются float64 с нулевой дробной
частью. И декодер JSON в Go следует этому принципу.
Пример
Набросок middleware, разбирающего тело запроса и записывающего результат разбора в контекст:
package api
import (
"context"
"encoding/json"
"net/http"
_ "github.com/eandr-67/errs"
v "github.com/eandr-67/validator"
s "github.com/eandr-67/validator/string"
)
// валидатор
var VL = v.Obj(v.NotNull).
Field("aaa", v.Int(v.Null, v.Gt[int64](25), v.Le[int64](50))).
Field("bbb", v.String(v.NotNull, s.Regex("^\\d{5}$"))).
Required("aaa").Default("bbb", "12345").Compile()
// middleware, использующая валидатор
func GetParams(next http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
defer func() { _ = r.Body.Close() }()
res, err := v.Parse(r.Body, VL) // Разбор тела запроса
if err != nil { // Возвращаем информацию об ошибке
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(err)
return
}
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "params", res.(map[string]any))))
},
)
}
- В фабрику
Obj, возвращающую построитель валидатора JSON-объекта (map[string]any), передаём действие NotNull,
запрещающее значение null.
- Вызываем методы построителя
Field, добавляющие поля к объекту.
- В первом случае в метод
Field передаём имя поля "aaa" и построитель валидатора целого числа (фабрика Int) c
действиями Null (значение null допустимо), Gt (больше), Le (меньше или равно).
- Во втором случае передаём имя поля "bbb" и построитель валидатора строки (фабрика
String) c действиями NotNull
и RegExp (проверка соответствия строки заданному регулярному выражению).
- Вызываем метод
Required, объявляющий поле aaa обязательными.
- Вызываем метод
Default, задающий для поля bbb значение по умолчанию "12345": если поле bbb отсутствует в JSON, оно
будет автоматически создано со значением "12345".
- Вызываем метод
Compile, собирающий валидатор из полученных построителем валидатора данных.
Таким образом, валидатор проверяет, что JSON содержит объект вида:
{
"aaa": 37,
"bbb": "01234"
}
, в котором поле aaa обязательно и должно содержать либо null, либо целое число в диапазоне (25; 50], а поле bbb
опционально и должно содержать строку из 5 цифр. Если поле bbb не задано, оно будет создано со значением "12345".
Регистрация ошибок
Для сбора ошибок, возникающих в процессе работы парсера-валидатора, используется модуль регистрации
ошибок errs. Он предназначен не специально для валидатора, а для API в целом и
реализует унифицированную структуру, удобную для отдачи as is в ответе API - без дополнительных преобразований.
На данный момент валидатор различает 8 видов ошибок, текстовые коды которых (тексты ошибок) сведены в массив ErrMsg.
Настройка собственных сообщений об ошибках сводится к изменению значений элементов этого массива.
Для человекочитаемой обработки ошибок создан набор констант с индексами элементов ErrMsg.
| Константа |
Код ошибки по умолчанию |
Описание ошибки |
| ErrTypeIncorrect |
"type" |
Сырое значение any не может быть преобразовано к требуемому типу |
| ErrFormatIncorrect |
"format" |
Строка имеет ошибочный формат: строка не может быть преобразована к требуемому типу либо строка не соответствует заданному регулярному выражению |
| ErrLengthIncorrect |
"length" |
Длина строки / массива / ассоциативного массива не соответствует заданным условиям |
| ErrValueIncorrect |
"value" |
Значение не соответствует заданным условием |
| ErrValueIsNull |
"null" |
Значение не может быть равно nil |
| ErrKeyMissed |
"missed" |
Отсутствует обязательный ключ ассоциативного массива (обязательное поле объекта JSON) |
| ErrKeyUnknown |
"unknown" |
Встречено неизвестный валидатору ключ ассоциативного массива (неизвестное поле объекта JSON) |
| ErrPanic |
"panic[%#v]" |
Возникновение паники внутри валидатора. |
В отличие от других кодов ошибок, представляющих собой просто строки, ошибка ErrPanic - шаблон для Sprintf, в который
передаются этот шаблон и recover().
Появление ErrPanic означает, что при создании схемы валидации был допущен логический ляп, скорее всего, связанный с
необработкой nil (null). Главный способ минимизации вероятности паники - первым действием каждого валидатора
(построителя валидатора) прописывать Null, NotNull или IfNull(...), как это сделано в тестовом примере.
Парсер
Механизм верхнего уровня. Предполагаемое применение (показанное в тестовом примере) - стадия обработки полученных
параметров запроса в конвейере middleware.
Получает исходный текстовый JSON и валидатор. Возвращает обработанные данные и регистратор ошибок.
Реализован в модуле в виде двух функций, различающихся источником JSON. Функция:
func Parse(reader io.Reader, vl Validator) (result any, err errs.Errors)
получает JSON из потока ввода, а функция:
func ParseStr(str string, vl Validator) (result any, err errs.Errors)
получает JSON из строки.
Валидатор
N.B. механизмы создания валидаторов описываются ниже - в разделе "Создание валидатора".
Реализует интерфейс:
type Validator interface { Do(raw any) (result any, err errs.Errors) }
Метод Do получает на вход "сырые" данные в виде значения типа any. Возвращает обработанные данные (опять же, в виде
значения типа any) и регистратор ошибок.
Работа валидатора начинается запуском преобразователя - функции, преобразующей входное значение типа any к указателю
на значение целевого типа. Если преобразователь регистрирует ошибки, работа валидатора завершается.
Иначе полученный указатель подаётся на вход конвейера, обрабатывающего значение. Конвейер состоит
из последовательности действий - функций, получающих указатель на значение и возвращающих указатель на обработанное
значение того же типа, который передается следующему действию и т.д. Указатель, возвращённый последним выполненным
действием, преобразуется в значение, которое обобщается в any и возвращается как результат обработки.
Таким образом, преобразования типов производятся только на входе и выходе валидатора, а основная работа производится с
типизированным значением.
Валидация простых значений
Любой валидатор, обрабатывающий значение, не имеющее значимой для процесса валидации внутренней структуры.
Представляет собой простой конвейер, все действия которого явно заданы при создании валидатора.
Любые ошибки записываются в регистратор с ключом "".
Валидация массивов
Преобразователь валидатора массива возвращает указатель на []any.
Конвейер валидатора массива можно разделить на 3 последовательные стадии:
- Начальные действия, явно задаваемые при создании валидатора и применяемые к самому массиву.
- Автоматически создаваемое скрытое действие, применяющее отдельный валидатор
cell к каждому элементу массива.
- Конечные действия, явно задаваемые при создании валидатора и применяемые к самому массиву.
Ошибки, возвращаемые cell, записываются в регистратор ошибок валидатора массива, но не останавливают работу конвейера
валидатора массива.
Ошибки начальных и конечных действий записываются в регистратор ошибок массива с ключом "". К ключам ошибок,
возвращаемых cell, добавляется префикс - индекс ошибочного элемента массива.
N.B. Я сознательно исключил из валидатора поддержку гетерогенных массивов: их использование - очевидная ошибка
архитектуры сервиса
Валидация объектов
Преобразователь валидатора массива возвращает указатель на map[string]any
Конвейер валидатора массива можно разделить на 5 последовательных стадий:
- Начальные действия, явно задаваемые при создании валидатора и применяемые к самому объекту.
- Автоматически создаваемое скрытое действие, проверяющее наличие в объекте полей, объявленных при создании валидатора
обязательными. Если обязательное поле не найдено, регистрируется ошибка
ErrKeyMissed с ключом - именем поля.
- Автоматически создаваемое скрытое действие, автоматически же создающее и инициализирующее отсутствующее в
обрабатываемом значении поля, объявленные при создании валидатора автосоздаваемыми.
- Автоматически создаваемое скрытое действие, применяющее валидаторы (отдельный валидатор для каждого поля) к каждому
существующему полю объекта. Если имя поля неизвестно валидатору, регистрируется ошибка
ErrKeyUnknown с ключом -
именем поля.
- Конечные действия, явно задаваемые при создании валидатора и применяемые к самому объекту.
Ошибки, возвращаемые всеми тремя скрытыми действиями, не останавливают работу конвейера.
Ошибки начальных и конечных действий записываются в регистратор ошибок объекта с ключом "". К ключам ошибок,
возвращаемых валидаторами полей, добавляется префикс - имя ошибочного поля.
Валидация uuid.UUID
Вынесена в отдельный пакет validator/uuid, т.к. для реализации UUID использует сторонний
пакет uuid. Но, по факту, ничем не отличается от других валидаторов простых значений.
Валидация time.Time
Вынесена в отдельный пакет validator/time.
Во первых, этот тип не является comparable, хотя, по факту, значения time.Time можно не только сравнивать, но и
упорядочивать. Но реализовано это не стандартными операциями Go, а методами типа. Потому, для time.Time пришлось
создавать свой собственный набор действий.
Во вторых, необходимо было реализовать поддержку шаблонов преобразования string -> time.Time.
В третьих, при работе с датой/временем существует большой геморрой под названием "часовой пояс".
Установка часового пояса
Производится функцией:
func SetTimeZone(tz *time.Location)
Часовой пояс устанавливается глобально - одновременно для всех валидаторов time.Time. Предполагается, что установка
будет производиться один раз - при старте приложения, использующего валидатор.
Работа с шаблонами даты/времени
Установка набора используемых шаблонов производится настройкой преобразователя в процессе сборки валидатора
построителем. Подробнее в разделе "Построитель валидатора времени/даты".
На всякий случай в пакете задана переменная Default, содержащая наиболее часто используемый в моей работе набор
шаблонов.
Преобразователь
Определяется типом:
type Converter[T any] func (raw any, err *errs.Errors) (value *T)
Поучает "сырое" значение типа any и указатель на регистратор ошибок. Возвращает указатель на преобразованное значение.
Если raw == nil, возвращает nil без ошибок. Именно потому используется указатель на значение, а не значение.
Если преобразование невозможно, регистрируется ошибка и возвращается указатель на неинициализированное значение.
Исключение - map[string]any, для которого возвращается &map[string]any{}.
Если ошибка произошла при конкретизации any (например, any -> string), регистрируется ошибка ErrTypeIncorrect.
Если ошибка произошла при преобразовании промежуточного конкретного типа в целевой тип (например,
string -> time.Time), регистрируется ошибка ErrFormatIncorrect.
N.B. Модуль validator не экспортирует преобразователи и при написании кастомных валидаторов необходимо будет
самостоятельно реализовать получатель, реализующий сигнатуру типа Conterter.
Действие
Функция - элементарный "кирпичик" процесса обработки значения. Определяется типом:
type Action[T any] func (value *T, err *errs.Errors) (newValue *T, continueProcessing bool)
Получает на вход указатель на обрабатываемое значение и указатель на регистратор ошибок. Возвращает указатель на
обработанное значение и флаг продолжения обработки. Если флаг равен true, возвращённое действием значение подаётся на
вход следующего действия конвейера. Если флаг равен false, выполнение конвейера досрочно завершается.
Только две функции реализуют непосредственно Action: Null и NotNull. Все остальные действия представляют собой
генерирующие функции, получающие на вход набор параметров и возвращающие настроенное этими параметрами замыкание типа
Action.
Обобщённые действия реализованы в самом пакете validator, действия, предназначенные для конкретных типов, вынесены в
отдельные пакеты.
Действия пакета validator.
| Действие |
Шаблон типа обрабатываемого значения (T) |
Ошибка |
Флаг продолжения |
Описание |
| Null |
any |
|
value != nil |
остановка конвейера если nil |
| NotNull |
any |
ErrValueIsNull |
value != nil |
ошибка если nil |
| IfNull(replace T) |
any |
|
true |
замена nil на replace |
| Eq(cmp T) |
comparable |
ErrValueIncorrect |
*value == cmp |
равно |
| Ne(cmp T) |
comparable |
ErrValueIncorrect |
*value != cmp |
не равно |
| In(cmps ...T) |
comparable |
ErrValueIncorrect |
*value in cmps |
входит в список |
| NotIn(cmps ...T) |
comparable |
ErrValueIncorrect |
*value not in cmps |
не входит в список |
| Lt(cmp T) |
ordered |
ErrValueIncorrect |
*value < cmp |
меньше |
| Le(cmp T) |
ordered |
ErrValueIncorrect |
*value <= cmp |
меньше или равно |
| Gt(cmp T) |
ordered |
ErrValueIncorrect |
*value > cmp |
больше |
| Ge(cmp T) |
ordered |
ErrValueIncorrect |
*value >= cmp |
больше или равно |
Действия пакета validator/string.
Тип обрабатываемого значения - string. Длина строки вычисляется не в байтах, а в символах кодировки UTF-8.
| Действие |
Ошибка |
Флаг продолжения |
Описание |
| Regex(pattern string) |
ErrFormatIncorrect |
match(pattern, *value) |
соответствует регулярке |
| NotRegex(pattern string) |
ErrFormatIncorrect |
!match(pattern, *value) |
не соответствует регулярке |
| LenEq(lng int) |
ErrLengthIncorrect |
len(*value) == lng |
длина равна |
| LenNe(lng int) |
ErrLengthIncorrect |
len(*value) != lng |
длина не равна |
| LenGe(lng int) |
ErrLengthIncorrect |
len(*value) >= lng |
длина больше или равна |
| LenLe(lng int) |
ErrLengthIncorrect |
len(*value) <= lng |
длина меньше или равна |
| LenIn(lngs ...int) |
ErrLengthIncorrect |
len(*value) in lngs |
длина входит в список |
| LenNotIn(lngs ...int) |
ErrLengthIncorrect |
len(*value) not in lngs |
длина не входит в список |
Действия пакета validator/array
Тип обрабатываемого значения - []any.
| Действие |
Ошибка |
Флаг продолжения |
Описание |
| LenEq(lng int) |
ErrLengthIncorrect |
len(*value) == lng |
длина равна |
| LenNe(lng int) |
ErrLengthIncorrect |
len(*value) != lng |
длина не равна |
| LenGe(lng int) |
ErrLengthIncorrect |
len(*value) >= lng |
длина больше или равна |
| LenLe(lng int) |
ErrLengthIncorrect |
len(*value) <= lng |
длина меньше или равна |
| LenIn(lngs ...int) |
ErrLengthIncorrect |
len(*value) in lngs |
длина входит в список |
| LenNotIn(lngs ...int) |
ErrLengthIncorrect |
len(*value) not in lngs |
длина не входит в список |
Действия пакета validator/time
Тип обрабатываемого значения - time.Time.
| Действие |
Ошибка |
Флаг продолжения |
Описание |
| Eq(cmp time.Time) |
ErrValueIncorrect |
*value == cmp |
равно |
| Ne(cmp time.Time) |
ErrValueIncorrect |
*value != cmp |
не равно |
| In(cmps ...time.Time) |
ErrValueIncorrect |
*value in cmps |
входит в список |
| NotIn(cmps ...time.Time) |
ErrValueIncorrect |
*value not in cmps |
не входит в список |
| Lt(cmp time.Time) |
ErrValueIncorrect |
*value < cmp |
меньше |
| Le(cmp time.Time) |
ErrValueIncorrect |
*value <= cmp |
меньше или равно |
| Gt(cmp time.Time) |
ErrValueIncorrect |
*value > cmp |
больше |
| Ge(cmp time.Time) |
ErrValueIncorrect |
*value >= cmp |
больше или равно |
Создание валидатора
Процесс создания валидатора состоит из 3 этапов:
- Создание построителя валидатора.
- Задание параметров валидатора - вызовами метода построителя.
- Сборка валидатора вызовом метода
Compile, рекурсивно вызывающего методы Compile вложенных построителей
валидаторов.
Построитель валидатора
Реализует интерфейс:
type Builder interface { Compile() Validator }
Метод Compile собирает валидатор из параметров, переданных построителю.
Создание построителя валидатора производится вызовом фабричной функции, возвращающей новый построитель заданного типа.
Три пакета модуля реализуют фабрики:
| Фабричная функция |
Тип данных построителя/валидатора |
Исходный тип данных JSON |
Пакет |
| String(actions ...Action[string]) *SimpleBuilder[string] |
string |
string |
validator |
| Bool(actions ...Action[bool]) *SimpleBuilder[bool] |
bool |
boolean |
validator |
| Float(actions ...Action[float64]) *SimpleBuilder[float64] |
float64 |
number |
validator |
| Int(actions ...Action[int64]) *SimpleBuilder[int64] |
int64 |
number |
validator |
| Any(actions ...Action[any]) *SimpleBuilder[any] |
any |
любой |
validator |
| UUID(actions ...Action[uuid.UUID]) *SimpleBuilder[uuid.UUID] |
uuid.UUID |
string |
validator/uuid |
| Time(formats []string, actions ...Action[time.Time]) *time.builder |
time.Time |
string |
validator/time |
| Arr(cell Builder, start ...Action[[]any]) *arrayBuilder |
[]any |
array |
validator |
| Obj(start ...Action[map[string]any]) *objectBuilder |
map[string]any |
object |
validator |
Все фабрики, кроме Arr и Time, принимают только набор действий (функций типа Action[T]), которые будут выполнены в
начале конвейера обработки значения, и возвращают указатель на построитель валидатора.
Параметры фабрик Arr и Time описываются в разделах, посвящённых построителям валидаторов массивов и даты/времени.
Фабрику Any можно, например, использовать для построения валидатора-пустышки, передающего любые данные с входа на
выход без каких-либо преобразований.
Простой построитель (String, Bool, Float, Int, Any, UUID)
Реализован типом SimpleBuilder[T any], собирающим валидатор простых значений. Содержит внутри себя единственный набор
действий.
Построитель имеет единственный дополнительный метод:
func (sb *SimpleBuilder[T]) Add(actions ...Action[T]) *SimpleBuilder[T]
Фабрика инициализирует набор действий построителя заданным при вызове фабрики перечислением действий. Вызовы метода
Add добавляют перечисленные в вызове действия в конец набора.
Таким образом, валидаторы v1-v9 полностью эквивалентны:
v1 := v.Int(v.Null, v.Gt[int64](25), v.Le[int64](50)).Compile()
v2 := v.Int(v.Null, v.Gt[int64](25), v.Le[int64](50)).Add().Compile()
v3 := v.Int(v.Null, v.Gt[int64](25)).Add(v.Le[int64](50)).Compile()
v4 := v.Int(v.Null).Add(v.Gt[int64](25)).Add(v.Le[int64](50)).Compile()
v5 := v.Int(v.Null).Add(v.Gt[int64](25), v.Le[int64](50)).Compile()
v6 := v.Int().Add(v.Null).Add(v.Gt[int64](25)).Add(v.Le[int64](50)).Compile()
v7 := v.Int().Add(v.Null).Add(v.Gt[int64](25), v.Le[int64](50)).Compile()
v8 := v.Int().Add(v.Null, v.Gt[int64](25)).Add(v.Le[int64](50)).Compile()
v9 := v.Int().Add(v.Null, v.Gt[int64](25), v.Le[int64](50)).Compile()
Построитель валидатора времени/даты.
Реализован типом time.builder.
Фабрика Time отличается тем, что первым параметром получает массив форматов даты/времени, который передаётся в
преобразователь создаваемого построителем валидатора.
На этапе разбора string -> time.Time преобразователь time.Time последовательно применяет полученные шаблоны к
строке, созданной на этапе any -> string преобразователя - пока один из них не подойдёт. Если все шаблоны вернули
ошибку, получатель регистрирует ошибку ErrFormatIncorrect.
В остальном построитель валидатора времени/даты не отличается от простых построителей.
Построитель валидатора массива
Реализован типом arrayBuilder. Содержит внутри себя два набора действий: начальный и конечный.
Фабрика Arr первым параметром получает построитель валидатора элементов массива. Перечисленные после
него действия добавляются в начальный набор действий. Конечный набор действий изначально пуст.
Если в качестве построителя валидатора элементов массива передано значение nil, обработка элементов массива
производиться будет.
Построитель содержит два дополнительных метода. Метод:
func (ab *arrayBuilder) Start(actions ...Action[[]any]) *arrayBuilder
добавляет перечисленные действия в конец набора начальных действий, а метод:
func (ab *arrayBuilder) Finish(actions ...Action[[]any]) *arrayBuilder
добавляет перечисленные методы в конец набора конечных действий.
Построитель валидатора объекта.
Реализован типом objectBuilder. Содержит внутри себя два набора действий: начальный и конечный, для заполнения которых
используются методы
func (ob *objectBuilder) Start(actions ...Action[map[string]any]) *objectBuilder
и
func (ob *objectBuilder) Finish(actions ...Action[map[string]any]) *objectBuilder
, полностью аналогичные одноимённым действиям arrayBuilder.
Помимо Start и Finish построитель валидатора объекта сдержит ещё пять методов, необходимых для настройки трёх
скрытых действий.
Добавление полей
Изначально построитель не содержит информации о полях объекта (объект без полей допустим, но имеет мало смысла).
Для добавления поля в построитель валидатора объекта используется метод:
func (ob *objectBuilder) Field(name string, builder Builder) *objectBuilder
В name передаётся имя поля, в builder - построитель валидатора значения этого поля. Если поле с именем name в
построителе уже существует, генерируется паника со строкой "field already exists". Если builder = nil, значение поля
не проверяется.
Для добавления сразу группы полей используется метод:
func (ob *objectBuilder) FieldList(fields map[string]Builder) *objectBuilder
В fields передаётся ассоциативный массив с ключами - именами полей и значениями - построителями валидаторов этих
полей. Внутри себя метод FieldList вызывает метод Field для каждой пары ключ-значение.
Объявление полей обязательными
По умолчанию все поля объекта опциональны и могут быть опущены в исходном JSON. Для того, чтобы объявить набор полей
обязательным, используется метод:
func (ob *objectBuilder) Required(fields ...string) *objectBuilder
, получающий набор имён полей и отмечающий эти имена в построителе как обязательные.
Данный метод можно вызывать многократно.
В процессе выполнения Compile() производится проверка того, что имена обязательных полей присутствуют в списке полей,
сформированном методами Field и FieldList (если поле не найдено, генерируется паника с текстом "required field does
unknown"), и того, что имена обязательных полей отсутствуют в списке полей по умолчанию (если поле присутствует в обоих
списках, генерируется паника с текстом "both required and default").
Объявление полей автосоздаваемыми
По умолчанию, если поля нет в исходном JSON, его не будет и в выходных данных валидатора. Но иногда бывает удобно
отсутствующие поля создать и инициализировать заданными значениями.
Поля создаются до запуска валидации отдельных полей, так что если поле создаётся, его значение в обязательном порядке
проходит валидацию - во избежание труднообнаруживаемых ошибок.
Для объявления одного поля автосоздаваемым используется метод:
func (ob *objectBuilder) Default(name string, value any) *objectBuilder
В name передаётся имя поля, в value - инициализирующее это поле значение. Если поле с таким именем уже объявлено
автосоздаваемым, генерируется паника с текстом "default already exists".
В процессе выполнения Compile() производится проверка того, что имена автосоздаваемых полей присутствуют в списке
полей, сформированном методами Field и FieldList и если поле не найдено, генерируется паника с текстом "default
field does unknown".
Для объявления группы полей автосоздаваемыми используется метод:
func (ob *objectBuilder) DefaultList(fields map[string]any) *objectBuilder
Ключи fields - имена полей, значения - инициализаторы этих полей. Внутри себя метод DefaultList вызывает Default
для каждой пары ключ-значение.
Создание кастомных валидаторов.
Валидатор произвольного типа создаётся вызовом функции:
func NewValidator[T any](converter Converter[T], actions ...Action[T]) Validator
, получающей преобразователь и перечисление всех действий, составляющих валидатор. Функция возвращает новый валидатор,
собранный из переданных параметров. Особенность работы функции в том, что все действия, равные nil, исключаются из
конвейера валидатора, что упрощает процесс подготовки набора действий перед его передачей в функцию.
Если converter == nil, генерируется паника со строкой "converter cannot be nil".
Кастомный преобразователь должен соответствовать правилам, перечисленным в разделе "Преобразователь".
В случаях, когда для создания валидатора достаточно функционала простого построителя, можно использовать функцию:
func NewSimpleBuilder[T any](c Converter[T], a ...Action[T]) *SimpleBuilder[T]
, получающую преобразователь и перечисление действий. Функция возвращает новый простой построитель валидатора заданного
типа, реализующий метод Add.