Опирается на правила: R-VLD-STD-1R-VLD-STD-5 и R-VLD-STD-X1R-VLD-STD-X3 из Validation Style Guide → раздел 2. Стандартные constraints.

Важно знать

  • validator/v10 читает теги validate:"..." на struct-полях; нет тегов — нет валидации.
  • required на non-pointer типе проверяет zero-value: для int ноль (0) тоже провалит required. Для числа-не-ноль используй gt=0.
  • Pointer-тип (*string, *int) + omitempty = «поле необязательно; если передано — проверить остальные теги».
  • Форматы email, e164, url, uuid4 — встроены; не пиши собственный regexp для них.
  • Деньги — int64 в копейках (или shopspring/decimal), не float64; числовой constraint gt=0, а не required.
  • min=N для строки — длина в символах (UTF-8 rune count через utf8.RuneCountInString); для числа — числовое значение.

validator/v10 — декларативная система тегов. Пара правил про zero-value и pointer-типы делает поведение предсказуемым: знаешь тип поля — знаешь, какой тег нужен.

Обязательные и опциональные поля

R-VLD-STD-1: граница между «поле обязательно» и «поле необязательно» строится через сочетание типа и тега.

type UpdateProductRequest struct {
    Name        string  `json:"name"         validate:"required,min=1,max=200"` // обязательно
    Description *string `json:"description"`                                    // опционально — nil = не задан
    Note        *string `json:"note"          validate:"omitempty,max=500"`     // необязательно, но если есть — не более 500
}

Логика:

  • string + validate:"required" — не-пустая строка (пустая "" провалит required).
  • *string без validate — допускается null/отсутствие в JSON.
  • *string + validate:"omitempty,max=500" — если поле передано и не nil, проверить max=500; если nil — пропустить.

Размеры

R-VLD-STD-2: строки — min=N,max=M; числа — min=N,max=M (или gt=0, gte=0, lte=M):

type CreateProductRequest struct {
    Name     string `json:"name"   validate:"required,min=1,max=200"`
    PriceCop int64  `json:"price"  validate:"gt=0,lte=100000000"` // в копейках, до 1 000 000 руб
    Stock    int    `json:"stock"  validate:"min=0,max=999999"`
    SKU      string `json:"sku"    validate:"required,min=4,max=20"`
}

Для int64 с min=N: validator/v10 применяет числовое сравнение, не длину строки — тип имеет значение.

Форматы

R-VLD-STD-3: использовать встроенные теги, не самописный regexp для известных форматов.

type CustomerRequest struct {
    Email       string  `json:"email"        validate:"required,email"`
    Phone       string  `json:"phone"        validate:"required,e164"`       // E.164: +79001234567
    Website     *string `json:"website"      validate:"omitempty,url"`
    ExternalID  string  `json:"external_id"  validate:"required,uuid4"`
    SberAccount *string `json:"sber_account" validate:"omitempty,min=20,max=20"` // расчётный счёт
}

Встроенные теги формата в validator/v10:

ТегЧто проверяет
emailадрес электронной почты
e164международный формат телефона (+7...)
urlполный URL с схемой
uriURI (без обязательной схемы)
uuid4UUID v4 в canonical форме
uuidUUID любой версии
hostname_porthost:port
ipIPv4 или IPv6 адрес
alphaтолько буквы
alphanumбуквы и цифры
numericтолько цифры

Время

R-VLD-STD-4: time.Time с тегом required обязывает передать корректное значение. Для future/past — кастомный тег (см. Custom constraints):

type CreateBookingRequest struct {
    StartsAt time.Time `json:"starts_at" validate:"required,future"` // future — custom
    EndsAt   time.Time `json:"ends_at"   validate:"required"`
    Duration int       `json:"duration"  validate:"required,min=30,max=480"` // минуты
}

time.Time — value-тип; zero-value (time.Time{}) провалит required. При декодировании JSON используй стандартный encoding/json — он обрабатывает RFC 3339. Если нужен кастомный формат, добавь UnmarshalJSON на типе или используй time.Time через json.Decoder с Layout.

Деньги

R-VLD-STD-5: деньги хранятся и передаются в минорных единицах (int64 в копейках) или через github.com/shopspring/decimal. Никогда float64 — потери точности при операциях.

type PaymentRequest struct {
    OrderID   string `json:"order_id"   validate:"required,uuid4"`
    AmountCop int64  `json:"amount_cop" validate:"gt=0"`            // копейки: 10000 = 100 руб
    Currency  string `json:"currency"   validate:"required,oneof=RUB USD EUR"`
}

gt=0 вместо required для числового поля-не-ноль: required на int64 провалится на значении 0, что семантически неверно если ноль допустим в некоторых контекстах. Для «обязательно и больше нуля» — validate:"gt=0".

Что запрещено

АнтипаттернПравилоЧто взамен
validate:"required" на int/int64 при допустимости нуляR-VLD-STD-X1validate:"gt=0" для «больше нуля»; min=0 для неотрицательного
validate:"regexp=^[a-zA-Z0-9._%+\\-]+@..." вместо emailR-VLD-STD-X2validate:"email" — встроенный тег
validate:"my_format" как один тег для нескольких стандартных правилR-VLD-STD-X3Явный набор тегов через запятую: required,min=1,max=200,email
float64 для суммы в рубляхR-VLD-STD-5int64 в копейках или shopspring/decimal
validate:"required" на time.Time без обработки zero-value при парсингеR-VLD-STD-4Убедиться, что JSON-парсер корректно декодирует ISO 8601 в time.Time

Куда дальше

  • Validation → раздел 2. Стандартные constraints — нормативные формулировки R-VLD-STD-*.
  • Custom constraints — когда встроенных тегов недостаточно.
  • Где валидировать — как применяются теги в httpreq.Decode.
  • Cross-field validation — правила между несколькими полями.