# Decision Record: Validation Library — go-playground/validator **Дата**: 2026-06-11 **Статус**: Accepted (требует реализации) ## Контекст Валидация во всех handlers делается вручную через `if req.Field == ""` проверки. Нет единой схемы, неконсистентные ошибки, нет валидации форматов. ## Решение **Использовать `github.com/go-playground/validator/v10`** — стандарт де-факто в Go. ### Почему не альтернативы: | Библиотека | Причина отказа | |------------|----------------| | go-ozzo/ozzo-validation | Функциональный стиль, менее популярный, меньше примеров | | Ручная валидация | Неподдерживаемо, баги, дублирование | | CUE / JsonSchema | Overengineering для REST API | ### План внедрения 1. **Добавить зависимость**: `go get github.com/go-playground/validator/v10` 2. **Создать пакет** `internal/validator/` с инициализацией и кастомными правилами 3. **Пометить DTO тегами** во всех handlers 4. **Добавить middleware/хелпер** для единообразных ответов об ошибках (RFC 7807) 5. **Покрыть тестами** кастомные валидаторы ### Кастомные валидаторы (понадобятся) ```go // internal/validator/validator.go func init() { Validate.RegisterValidation("coordinate", validateCoordinate) Validate.RegisterValidation("place_type", validatePlaceType) Validate.RegisterValidation("user_role", validateUserRole) Validate.RegisterValidation("currency", validateCurrency) // ISO 4217 } func validateCoordinate(fl validator.FieldLevel) bool { v := fl.Field().Float() return v >= -90 && v <= 90 // для lat; для lng нужен отдельный или min/max } func validatePlaceType(fl validator.FieldLevel) bool { v := fl.Field().String() return v == "place" || v == "studio" } func validateUserRole(fl validator.FieldLevel) bool { roles := map[string]bool{"customer": true, "landlord": true, "executor": true, "moderator": true, "superadmin": true} return roles[fl.Field().String()] } ``` ### Пример DTO с тегами ```go type CreatePlaceRequest struct { Title string `json:"title" validate:"required,min=1,max=255"` Description *string `json:"description" validate:"omitempty,max=5000"` Address *string `json:"address" validate:"omitempty,max=500"` Lat float64 `json:"lat" validate:"required,coordinate,min=-90,max=90"` Lng float64 `json:"lng" validate:"required,coordinate,min=-180,max=180"` Type string `json:"type" validate:"required,place_type"` Tags []string `json:"tags" validate:"dive,required,alphanum,max=50"` Features []string `json:"features" validate:"dive,required,alphanum,max=50"` HourlyRate *int `json:"hourly_rate" validate:"omitempty,min=0"` Currency string `json:"currency" validate:"omitempty,len=3,uppercase,currency"` MinHours int `json:"min_hours" validate:"min=0"` } ``` ### RFC 7807 Problem Details ответ ```json { "type": "https://api.photoplaces.ru/errors/validation-error", "title": "Validation Failed", "status": 422, "detail": "One or more fields failed validation", "instance": "/api/v1/places", "errors": [ { "field": "title", "message": "title is required" }, { "field": "lat", "message": "lat must be between -90 and 90" } ] } ``` ## Последствия **Положительные:** - Декларативная валидация в DTO - Консистентные ошибки - Легко тестировать - Стандарт индустрии **Отрицательные:** - Рефлексия (небольшой оверхед ~1-2µs) - Нужно обновить все handlers ## Связанные заметки - [[backend-validation]] - [[architecture-overview]] ## Теги #decision-record #validation #go #best-practice