Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 3 из 25 ~18 мин +20 XP

Теги required, min, max

Теги required, min и max закрывают половину правил любой формы: обязательность, длины и диапазоны. В этом уроке — живая форма профиля: где required ловит ноль вместо «заполнено», почему min для строк считает руны, а не байты, как omitempty делает поля необязательными и зачем на числах нужен указатель. Каждый тег разбираем с граблями, которые обычно находят только в проде.

Содержание урока · 10 разделов

Скелет формы уже готов с прошлого урока: структура, конструктор валидатора, вызов Struct. Теперь наполняем правила. Наша форма профиля — ProfileForm с четырьмя полями: Username, Password, Age и необязательный About. Для каждого поля зададим комбинацию обязательности и границ, а попутно разберём главный сюрприз этой триады: required проверяет не «заполнено ли», а «не нулевое ли значение», и именно на этом зазоре живут самые частые баги новичков. Диапазоны тоже не так просты, как выглядят: min и max меняют смысл в зависимости от типа поля, и это надо выучить один раз — на всю карьеру с валидатором. План урока такой: сначала required и его зазор между «нулевым» и «заполненным», затем полиморфизм min и max с таблицей по типам, дальше omitempty и указатели как два способа сказать «поле необязательное». Каждому тегу — своя грабля из практики, и к концу урока вы соберёте чек-лист правил, которым будете пользоваться во всех следующих формах курса.

validator required tag: не-нулевое, а не «заполненное»#

required звучит как «поле заполнено», но реализовано строго: поле не должно быть нулевым значением своего типа. Для строк это пустая строка — и только: строка из пробелов required пройдёт, потому что она не пустая. Для чисел — ноль, для bool — false, для указателей и интерфейсов — nil, для срезов и карт — nil или нулевая длина. Отсюда первая крупная ловушка курса: числовое поле, где ноль — легитимное значение. Количество детей, этаж, скидка в процентах — во всех этих случаях required отвергнет честный ноль, и пользователь формы получит «обязательное поле», хотя ничего не забыл. То же самое с деньгами: сумма перевода 0 на int с required уедет в отказ, хотя пользователь мог ввести ноль осознанно — перевод между своими счетами без комиссии. Различать «не указано» и «указан ноль» можно только указателем: *int с required,gte=0 пропустит честный ноль и отвергнет отсутствующее значение. Решений два: пересмотреть, действительно ли ноль допустим (если нет — нужен диапазон вроде gte=1), либо перейти на указатель, где nil и ноль различимы. И вторая ловушка — строка из пробелов: required её пропустит, а бизнес примет «имя пользователя» из одних пробелов. Отсюда правило: чистка пробелов — это нормализация перед валидацией, у неё отдельный урок курса. Третье следствие из семантики required — пустые контейнеры: срез тегов длиной ноль required отвергнет так же, как ноль числа; если пустой список легитимен, поле описывается парой omitempty,min=1 — «пусто можно, непусто хотя бы один элемент».

Форма профиля: четыре профиля правил

GO
type ProfileForm struct {
        Username string   `validate:"required,min=3,max=32"`
        Password string   `validate:"required,min=8,max=72"`
        Age      *int     `validate:"required,gte=14"`
        About    string   `validate:"omitempty,max=500"`
}

// Три поля обязательны, About — нет.
if err := validate.Struct(&form); err != nil {
        // ValidationErrors: по записи на каждое сломанное поле
}

Четыре поля — четыре рабочих комбинации. Username и Password обязательны с диапазоном длины; Age — указатель с required: nil означает «не указан», а не нулевой возраст, и диапазон gte проверяется по значению после разыменования; About необязателен, но непустой — не длиннее пятисот символов. Каждую комбинацию разберём по отдельности ниже.

min max валидация: три смысла одного тега#

min и max — самые полиморфные теги валидатора: их смысл зависит от типа поля. Для строк они считают длину, причём в рунах, а не в байтах: «привет» — семь символов, хотя байтов двенадцать; для кириллических форм это принципиально, потому что len() по байтам дал бы вдвое больше. Для чисел min и max сравнивают само значение: min=1 на int отвергает ноль и отрицательные числа. Для срезов, карт и массивов — количество элементов: min=1 на []string требует хотя бы один элемент. Границы у min/max включительные: значение ровно на границе проходит проверку. Если нужна строгая граница, берите gt и lt, а нестрогие аналоги gte и lte для чисел работают как «минимально/максимально допустимое». Точный размер даёт len=13 — например, для строк фиксированной длины вроде штрихкодов и кодов подтверждения. На практике полиморфизм стреляет вот где: команда пишет max=140, думая о лимите твита, а поле — заголовок задачи в символах; для латиницы лимит совпадает с подсчётом фронтенда, для кириллицы — совпадает тоже (руны везде), а вот если фронт считает байтами — начинается спор про «пятьдесят символов» против «сотни байтов». Договаривайтесь на рунах и фиксируйте это в доках API. Таблица ниже собирает всю матрицу «тег против типа» на один экран — вернитесь к ней, когда сомневаетесь.

Поведение тега по типам поля#

ТегСтрокаЧислоСрез/картаУказатель
requiredне пустаяне 0не nil и не пустойне nil
min=Nдлина ≥ N рунзначение ≥ Nэлементов ≥ Nправило по типу за разыменованием
max=Nдлина ≤ N рунзначение ≤ Nэлементов ≤ Nправило по типу за разыменованием
gt=N / lt=N—строго больше / меньше—правило по типу за разыменованием
len=Nровно N рун—ровно N элементов—
omitemptyнулевой — остальные правила пропускаютсято жето жеnil пропускается

Проверка: руны или байты

Строка «Йо» и тег min=3. Пройдёт ли проверка?

omitempty: компаньон опциональных полей#

Необязательное поле — не то же самое, что поле без правил. Поставили min=3 на необязательное поле — и пустая строка провалится: min проверяет длину, а нулевая длина меньше трёх. omitempty решает конфликт: если поле нулевое значение своего типа, все остальные правила тега пропускаются. Комбинация omitempty,min=3,max=500 читается как договор: пусто — можно; непусто — от трёх до пятисот символов. Порядок правил в теге роли не играет — omitempty может стоять и в конце, — но конвенция ставить его первым улучшает читаемость. Частая ошибка новичка противоположна: привычка вешать omitempty на всё подряд, включая обязательные поля. required и omitempty в одном теге противоречат друг другу: required отвергает нулевое, которое omitempty только что пропустил, — поведение получается запутанным и зависит от порядка исполнения правил. Выбирайте одно из двух: поле обязательное или нет. Отдельный кейс для omitempty — PATCH-обновления: клиент присылает только изменённые поля, остальное отсутствует, и после декодирования структура заполнена нулями. Без omitempty валидация всей структуры отвергла бы такие запросы пачкой; с omitempty на необязательных полях и точечным перечислением обязательных через StructPartial — метод из прошлого урока — частичные обновления описываются без отдельной структуры на каждый эндпоинт. И ещё разница, о которой забывают: поле, отсутствующее в JSON, и поле с пустой строкой после декодирования для валидатора неразличимы — оба нулевые. Поэтому omitempty честно описывает оба случая сразу, а вот проверка «пользователь прислал, но пустое» тегами не решается — нужны указатели или приёмы из урока про DTO-границы.

Указатели: nil, ноль и «не указано»

GO
// nil и «не указано» — разные вещи, и указатель это различает.
type MeetingForm struct {
        Attendees *int `validate:"required,gte=2,lte=50"`
}

// {"attendees":null}  → required: nil-указатель, значения нет
// {"attendees":0}     → gte=2: число указано, но 2..50 не попало
// {"attendees":12}    → прошло: 12 в диапазоне

На указателе required означает «не nil»: поле обязали указать, а ноль внутри — конкретное значение, которое проверит gte. Без указателя int не различил бы «не прислали» и «прислали ноль» — после декодирования оба выглядят нулём. Бонус связки: после успешной проверки Attendees гарантированно не nil, и разыменование в бизнес-коде безопасно.

Точные границы: len, eq, ne, gt и lt#

Между «от» и «до» живёт семья точных проверок. len=N требует точного размера: строка из тринадцати символов для штрихкода, срез ровно из шести цифр кода подтверждения. eq и ne сравнивают значение с константой: eq=0 удобен для «поле должно быть нулём» в статусных структурах, ne — для запрета конкретных значений, хотя чаще список запретов переворачивают в oneof с перечнем допустимых. Для чисел полезна пара gt и lt — строгие границы: gt=0 исключает ноль там, где min=0 его пропускает, и это спасает от очередного бага «минимальное количество — ноль». Все эти теги подчиняются той же логике типов: строки считают руны, числа сравнивают значения, срезы — количество элементов. И так же, как min и max, они не прощают нулевых значений без omitempty: точная проверка на необязательном поле требует пары с omitempty, иначе пустая строка провалит len=6. Границы честнее проверять на граничных значениях: min=3 пропускает ровно три символа и ровно три элемента — тест со строкой из трёх символов скажет о правиле больше, чем чтение документации. Если поведение тега кажется неочевидным, соберите его локально на пяти значениях: ниже границы, на границе, выше, нулевое и пустое — эта пятёрка вскрывает любой полиморфный тег быстрее, чем чтение чужих туториалов.

Что вернёт проверка формы

Прочитайте код и выберите, что он выведет:

type TicketForm struct {
        Promo string `validate:"omitempty,min=4,max=16"`
        Seats int    `validate:"min=1,max=6"`
}

in := TicketForm{Promo: "", Seats: 1}
err := validate.Struct(&in)
// что в err?

Один тег — цепочка правил через запятую#

Внутри тега правила разделяются запятыми и исполняются слева направо до первого провала: validate:"required,min=3,max=32" — сначала required, потом min, потом max; споткнувшись, поле завершает проверку, и остальные правила не выполняются. Параметр правила идёт через знак равенства: min=3. Пробелы вокруг правил — источник тихих сюрпризов: пробел после запятой превращает min=3 в неизвестный тег с именем « min», и валидатор паникует Undefined validation function на первом же прогоне структуры — форматируйте теги без пробелов. Перечень допустимых значений задаёт oneof, где значения разделяются уже пробелами: oneof=admin editor viewer — внутри одного тега грамматика двойная, запятые между правилами, пробелы внутри перечня. Форматы вроде email и datetime разобраны в следующем уроке, а здесь напомним про нормализацию: правила работают с тем, что им дали, — trim и нижний регистр делайте до validate.Struct, иначе « a@b.ru » придётся описывать тегами на каждый случай.

Частые грабли required и min/max#

  • min на строке считает руны, а не байты — не сверяйте его с len()
  • required на числе отвергает честный ноль — если ноль легитимен, берите указатель или omitempty
  • omitempty и required в одном теге противоречат друг другу — выберите одно
  • пробел после запятой в теге — неизвестный тег и паника Undefined validation function
  • min и max включительные: для строгой границы нужны gt и lt
  • min на срезе требует элементы, а не символы внутри строк — для элементов есть dive

Проверка: required на булевом поле

Поле Confirmed bool с тегом required. Пользователь не поставил галочку. Что вернёт валидатор?

Соберите правила формы

Заполните пропуски: обязательность имени, опциональность описания, нижняя граница возраста и точная длина кода.

type ProfileForm struct { Username string `validate:",min=3,max=32"` About string `validate:",max=500"` Age *int `validate:"required,=14"` Code string `validate:"omitempty,=6"` // ровно 6 символов }

Соберите путь формы через валидатор

Клиент отправил имя из одной буквы и не указал возраст. Разложите события по порядку.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: триада тегов

Термин

required

нажмите, чтобы перевернуть
Определение

Не-нулевое значение типа: не "", не 0, не false, не nil, не пустой срез. Честный ноль числа он не отличит от «не указано» — для этого нужен указатель.

1 / 5

Следующий урок — форматы: email, url, uuid и datetime: длины и диапазоны разобрали, переходим к проверке синтаксиса значений без ручных регулярных выражений. Ошибки, которые возвращают эти теги, разбираем в пятом уроке — методы Field, Tag и Param превращают провал min в понятный ответ пользователю. Сообщения на русском собираются в шестом уроке, а чистка пробелов перед проверками живёт в отдельном уроке про нормализацию. Код формы соберите локально: песочница сайта исполняет только чистый Go без внешних модулей, поэтому валидатор живёт в вашем терминале, а интерактивы закрепляют правила без запуска.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • required — не-нулевое значение типа: пустая строка, 0, false, nil, пустой срез; честный ноль он не отличит от «не указано».
  • min и max полиморфны: руны у строк, значение у чисел, элементы у срезов; границы включительные, строгие — gt и lt.
  • min для строк считает руны, а не байты: кириллица не удваивает длину, len() — удваивает.
  • omitempty пропускает остальные правила на нулевом поле; в паре с required противоречит сам себе.
  • Указатель на число различает «не указано» и ноль; required на *T — это «не nil».
  • Правила в теге идут через запятую до первого провала; пробел после запятой — паника Undefined validation function.

Домашнее задание

Возьмите форму из урока и добавьте два поля: Promo string с omitempty,min=4,max=16 и Seats *int с required,gte=1,lte=6. Прогоните локально четыре случая: оба заполнены валидно, Promo пуст, Seats = 0, Seats = nil. Запишите, какие теги сработали в каждом случае, и объясните себе, почему Seats = 0 и Seats = nil дали разные ошибки.

Личные заметкиПусто
Хранятся только в вашем браузере (Validator · Теги required, min, max)

Вопросы и ответы по уроку

Почему required отвергает 0, хотя это легитимное значение?

required проверяет нулевое значение типа, а ноль числа — нулевое значение. Если ноль допустим по смыслу, различите «не указано» и ноль указателем *T, либо разрешите пустоту omitempty и проверяйте диапазон только при наличии значения. Оба приёма разобраны в уроке выше — на Age и About.

min=3 считает байты или символы?

Символы (руны). «Йо» — две руны и четыре байта, min=3 её не пропустит. Правила для строк считают длину в рунах, поэтому кириллица не даёт двойную длину, как это делает len(). Если фронтенд считает лимиты по-своему, разногласия вскрываются именно на кириллице — сверяйте подсчёт и фиксируйте правило в доках API.

Можно ли совместить required и omitempty?

Не стоит: omitempty пропускает правила на нулевом поле, а required требует ненулевое — конфликт тегов делает поведение запутанным. Выберите одно: поле обязательное (required) или необязательное (omitempty плюс правила).

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Asynq25 уроков~11 чпрактика25 уроков уже доступны

Asynq в Go: очередь фоновых задач на Redis

Курс asynq для Go: клиенты и воркеры, отложенные и периодические задачи, ретраи, очереди с приоритетами, агрегация, Inspector и asynqmon. 25 уроков.

  • Строить клиент и воркер asynq и подключать Redis
  • Отправлять отложенные, периодические и уникальные задачи
  • Настраивать ретраи, таймауты и приоритеты очередей
  • Мониторить задачи через Inspector и asynqmon