backend-validation.md 5.1 KB

Backend: Валидация входящих данных

Контекст: Во всех handlers (places.go, services.go, auth.go и др.) валидация делается вручную через if req.Field == "" проверки. Нет единой схемы валидации, нет структурированных ошибок.

Суть

Ручная валидация — не масштабируема и ошибочна:

  • Дублирование кода проверок
  • Неконсистентные сообщения об ошибках
  • Нет валидации типов, форматов (email, URL, UUID, координаты)
  • Сложно поддерживать и тестировать

Код (проблемное место)

// backend/internal/handlers/places.go:120-147
func (h *PlaceHandler) Create(w http.ResponseWriter, r *http.Request) {
    var req createPlaceRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeError(w, http.StatusBadRequest, "invalid request body")
        return
    }

    if req.Title == "" {           // Ручная проверка
        writeError(w, http.StatusBadRequest, "title is required")
        return
    }
    if req.Type == "" {            // Ручная проверка
        writeError(w, http.StatusBadRequest, "type is required")
        return
    }
    // ... нет валидации: lat/lng range, URL формат, enum для type, длина строк
}

Решение: go-playground/validator v10

Стандарт де-факто в Go экосистеме. Поддерживает:

  • Struct tags: validate:"required,email,max=255"
  • Custom validators
  • Перевод ошибок
  • Вложенные структуры

Пример внедрения:

// internal/validator/validator.go
package validator

import (
    "github.com/go-playground/validator/v10"
)

var Validate *validator.Validate

func init() {
    Validate = validator.New()
    // Регистрация кастомных валидаторов
    Validate.RegisterValidation("coordinate", validateCoordinate)
}

func validateCoordinate(fl validator.FieldLevel) bool {
    lat := fl.Field().Float()
    return lat >= -90 && lat <= 90
}
// internal/handlers/places.go — DTO с тегами
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,oneof=place studio"`
    Tags        []string `json:"tags" validate:"dive,required,alphanum"`
    Features    []string `json:"features" validate:"dive,required,alphanum"`
    HourlyRate  *int     `json:"hourly_rate" validate:"omitempty,min=0"`
    Currency    string   `json:"currency" validate:"omitempty,len=3,uppercase"`
    MinHours    int      `json:"min_hours" validate:"min=0"`
}
// В handler:
func (h *PlaceHandler) Create(w http.ResponseWriter, r *http.Request) {
    var req createPlaceRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeError(w, http.StatusBadRequest, "invalid request body")
        return
    }

    if err := validator.Validate.Struct(req); err != nil {
        var ve validator.ValidationErrors
        if errors.As(err, &ve) {
            writeValidationError(w, ve)  // Структурированный ответ
            return
        }
        writeError(w, http.StatusBadRequest, err.Error())
        return
    }
    // ...
}

Структурированный ответ об ошибках (RFC 7807 / Problem Details)

{
  "type": "https://api.photoplaces.ru/errors/validation-error",
  "title": "Validation Failed",
  "status": 422,
  "detail": "One or more fields failed validation",
  "errors": [
    { "field": "title", "message": "title is required" },
    { "field": "lat", "message": "lat must be between -90 and 90" }
  ]
}

Альтернативы

Библиотека Плюсы Минусы
go-playground/validator (рекомендую) Стандарт, быстрый, теги, кастомные правила Рефлексия (небольшой оверхед)
go-ozzo/ozzo-validation Функциональный стиль, без рефлексии Менее популярный
Ручная (текущее) Нет зависимостей Неподдерживаемо, баги

Связанные заметки

  • [[decision-validation-library]]
  • [[backend-auth-security]]
  • [[architecture-overview]]

Источник

Задача: Code review PhotoPlaces — отсутствие валидации входных данных

Теги

#backend #validation #go #best-practice #technical-debt