Библиотека validator для Go: полное руководство
1. Что это и зачем нужна
Библиотека validator (полное имя пакета — github.com/go-playground/validator/v10) — это мощный и гибкий инструмент для валидации структур и отдельных полей в языке Go. Она позволяет декларативно описывать правила проверки данных прямо в тегах структур, что делает код чистым, читаемым и легко поддерживаемым. Вместо того чтобы писать десятки строк ручных проверок (if-else, switch), вы просто добавляете теги к полям, и библиотека делает всю работу за вас.
Зачем это нужно? В любом веб-приложении, API или сервисе, работающем с пользовательским вводом, критически важно проверять данные до их обработки. Некорректный email, пустое имя, отрицательный возраст, слишком длинный пароль — всё это может привести к ошибкам в логике, уязвимостям (SQL-инъекции, XSS) или падению сервера. validator решает эту проблему элегантно: вы определяете правила один раз, а затем вызываете одну функцию Validate.Struct(), которая проверяет все поля автоматически.
Библиотека поддерживает десятки встроенных валидаторов (required, min, max, email, url, uuid, oneof, len и многие другие), а также позволяет создавать собственные кастомные валидаторы. Она активно используется в продакшене, имеет отличную производительность (благодаря кэшированию рефлексии) и совместима с популярными фреймворками, такими как Gin, Echo и Fiber. Если вы пишете на Go и хотите избежать boilerplate-кода, эта библиотека — must-have.
2. Установка
Установка библиотеки производится стандартным способом через go get. Убедитесь, что у вас установлен Go версии 1.18 или выше (рекомендуется 1.21+).
go get github.com/go-playground/validator/v10
После выполнения команды пакет будет добавлен в ваш go.mod. Импортируйте его в коде:
import "github.com/go-playground/validator/v10"
3. Быстрый старт — минимальный рабочий пример
Давайте создадим простую структуру пользователя и проверим её поля. Это минимальный пример, который демонстрирует основную идею.
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// User — структура с тегами валидации
type User struct {
Name string `validate:"required,min=2,max=50"`
Email string `validate:"required,email"`
Age int `validate:"gte=0,lte=150"`
}
func main() {
// Создаём экземпляр валидатора
validate := validator.New()
// Тестовые данные
user := User{
Name: "A", // Слишком короткое имя
Email: "invalid-email", // Невалидный email
Age: -5, // Отрицательный возраст
}
// Выполняем валидацию
err := validate.Struct(user)
if err != nil {
// Проходим по всем ошибкам
for _, err := range err.(validator.ValidationErrors) {
fmt.Printf("Поле: %s, Ошибка: %s, Значение: %v\n",
err.Field(), err.Tag(), err.Value())
}
} else {
fmt.Println("Валидация пройдена успешно!")
}
}
Вывод программы:
Поле: Name, Ошибка: min, Значение: A
Поле: Email, Ошибка: email, Значение: invalid-email
Поле: Age, Ошибка: gte, Значение: -5
Как видите, библиотека автоматически находит все нарушения и возвращает структурированную информацию о каждой ошибке.
4. Основные методы и функции
Ниже приведены 8 ключевых методов и функций библиотеки. Для каждого указана сигнатура, описание и рабочий пример.
4.1. validator.New()
Сигнатура: func New() *Validate
Что делает: Создаёт новый экземпляр валидатора. Это основной объект, через который выполняются все проверки. Рекомендуется создавать один экземпляр на всё приложение (singleton).
validate := validator.New()
// Теперь можно вызывать validate.Struct(), validate.Var() и т.д.
4.2. Validate.Struct()
Сигнатура: func (v *Validate) Struct(s interface{}) error
Что делает: Валидирует все поля переданной структуры в соответствии с тегами validate. Возвращает nil, если ошибок нет, или ValidationErrors (срез ошибок).
type Product struct {
Name string `validate:"required"`
Price float64 `validate:"gte=0"`
}
p := Product{Name: "", Price: -10.5}
err := validate.Struct(p)
if err != nil {
fmt.Println(err)
}
4.3. Validate.Var()
Сигнатура: func (v *Validate) Var(field interface{}, tag string) error
Что делает: Валидирует одно значение (не структуру) по указанному тегу. Полезно для проверки отдельных переменных, например, строк или чисел.
email := "test@example"
err := validate.Var(email, "required,email")
if err != nil {
fmt.Println("Email невалиден:", err)
} else {
fmt.Println("Email корректен")
}
4.4. Validate.VarWithValue()
Сигнатура: func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) error
Что делает: Валидирует поле относительно другого значения. Используется для сравнения, например, проверка совпадения паролей или диапазона дат.
password := "secret123"
confirm := "secret456"
err := validate.VarWithValue(confirm, password, "eqfield")
if err != nil {
fmt.Println("Пароли не совпадают:", err)
}
4.5. Validate.RegisterValidation()
Сигнатура: func (v *Validate) RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error
Что делает: Регистрирует кастомный валидатор с уникальным именем тега. Функция fn должна иметь сигнатуру func(fl validator.FieldLevel) bool.
// Кастомный валидатор: строка должна содержать слово "go"
validate.RegisterValidation("contains_go", func(fl validator.FieldLevel) bool {
value := fl.Field().String()
return strings.Contains(value, "go")
})
type Post struct {
Title string `validate:"required,contains_go"`
}
p := Post{Title: "Hello World"}
err := validate.Struct(p)
if err != nil {
fmt.Println(err) // Ошибка: Title не содержит "go"
}
4.6. Validate.RegisterAlias()
Сигнатура: func (v *Validate) RegisterAlias(alias, tags string)
Что делает: Создаёт псевдоним (алиас) для комбинации тегов. Упрощает повторное использование сложных правил.
// Алиас "notempty" = "required" + "min=1"
validate.RegisterAlias("notempty", "required,min=1")
type Config struct {
APIKey string `validate:"notempty"`
}
c := Config{APIKey: ""}
err := validate.Struct(c)
if err != nil {
fmt.Println(err) // Ошибка: APIKey не соответствует required или min
}
4.7. Validate.RegisterCustomTypeFunc()
Сигнатура: func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{})
Что делает: Регистрирует функцию для извлечения значения из кастомного типа (например, обёртки над временем или ID). Позволяет валидировать поля, которые не являются примитивами.
type MyTime struct {
time.Time
}
// Функция извлечения значения
validate.RegisterCustomTypeFunc(func(field reflect.Value) interface{} {
if t, ok := field.Interface().(MyTime); ok {
return t.Unix()
}
return nil
}, MyTime{})
type Event struct {
Start MyTime `validate:"required"`
}
4.8. Validate.RegisterStructValidation()
Сигнатура: func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{})
Что делает: Регистрирует валидатор на уровне всей структуры. Позволяет проверять взаимосвязи между полями (например, дата начала не позже даты окончания).
type DateRange struct {
Start string `validate:"required"`
End string `validate:"required"`
}
validate.RegisterStructValidation(func(sl validator.StructLevel) {
dr := sl.Current().Interface().(DateRange)
if dr.Start > dr.End {
sl.ReportError(dr.Start, "Start", "Start", "before_end", "")
}
}, DateRange{})
dr := DateRange{Start: "2025-03-01", End: "2025-02-01"}
err := validate.Struct(dr)
if err != nil {
fmt.Println(err) // Ошибка: Start не соответствует before_end
}
5. Реальный практический пример
Рассмотрим полноценный пример: REST API для регистрации пользователя с валидацией всех полей, включая кастомные проверки.
package main
import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/go-playground/validator/v10"
)
// RegisterRequest — структура запроса на регистрацию
type RegisterRequest struct {
Username string `json:"username" validate:"required,min=3,max=30,alphanum"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8,max=64"`
ConfirmPass string `json:"confirm_password" validate:"required,eqfield=Password"`
Age int `json:"age" validate:"gte=18,lte=120"`
Phone string `json:"phone" validate:"required,e164"`
Country string `json:"country" validate:"required,iso3166_1_alpha2"`
AgreedToTerms bool `json:"agreed_to_terms" validate:"eq=true"`
ReferralCode string `json:"referral_code" validate:"omitempty,len=8"`
}
// Response — структура ответа API
type Response struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Errors []FieldError `json:"errors,omitempty"`
}
type FieldError struct {
Field string `json:"field"`
Tag string `json:"tag"`
Value string `json:"value"`
}
var validate *validator.Validate
func init() {
validate = validator.New()
// Кастомный валидатор для проверки сложности пароля
validate.RegisterValidation("password_strength", func(fl validator.FieldLevel) bool {
password := fl.Field().String()
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasDigit := regexp.MustCompile(`[0-9]`).MatchString(password)
hasSpecial := regexp.MustCompile(`[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]`).MatchString(password)
return hasUpper && hasLower && hasDigit && hasSpecial
})
// Регистрируем кастомный тег для пароля
validate.RegisterAlias("strongpassword", "required,min=8,max=64,password_strength")
}
func registerHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var req RegisterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(Response{
Success: false,
Message: "Invalid JSON body",
})
return
}
// Валидация структуры
err := validate.Struct(req)
if err != nil {
var fieldErrors []FieldError
for _, verr := range err.(validator.ValidationErrors) {
fieldErrors = append(fieldErrors, FieldError{
Field: verr.Field(),
Tag: verr.Tag(),
Value: fmt.Sprintf("%v", verr.Value()),
})
}
w.WriteHeader(http.StatusUnprocessableEntity)
json.NewEncoder(w).Encode(Response{
Success: false,
Message: "Validation failed",
Errors: fieldErrors,
})
return
}
// Симуляция успешной регистрации
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Response{
Success: true,
Message: fmt.Sprintf("User %s registered successfully", req.Username),
})
}
func main() {
http.HandleFunc("/api/register", registerHandler)
server := &http.Server{
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
fmt.Println("Server started on :8080")
if err := server.ListenAndServe(); err != nil {
fmt.Println("Server error:", err)
}
}
Пример тестового запроса (через curl):
curl -X POST http://localhost:8080/api/register \
-H "Content-Type: application/json" \
-d '{
"username": "john_doe",
"email": "john@example.com",
"password": "Str0ng!Pass",
"confirm_password": "Str0ng!Pass",
"age": 25,
"phone": "+1234567890",
"country": "US",
"agreed_to_terms": true,
"referral_code": "ABCD1234"
}'
Ответ при успехе:
{"success":true,"message":"User john_doe registered successfully"}
Ответ при ошибке (например, несовпадение паролей):
Также в библиотеке
HTTP-клиент для C# (.NET): топ библиотек для работы с веб-запросами
Генерация PDF-отчётов на Go: обзор библиотек и примеры кода
Ehcache для Java: полное руководство с примерами