TGViewer
Библиотека Go-разработчика | Golang Библиотека Go-разработчика | Golang @goproglib · 24.1K subscribers
Post #7125 3.65K
🧑‍💻 Валидация данных в Go

Валидация — одна из тех вещей, где легко утонуть в шаблонном коде. Отдельные функции на каждый тип, разбросанные проверки по всему проекту, непонятно где и что смотреть при баге.

Идея

Определяем интерфейс Validator. Любой тип, который его реализует, умеет сам себя проверять:
type Validator interface {
Validate() map[string]string
}


Вместо сырой map[string]string удобнее завести отдельный тип Problems:
type Problems map[string]string

func (p Problems) Add(field, msg string) {
p[field] = msg
}

func (p Problems) String() string {
if len(p) == 0 {
return ""
}
msgs := make([]string, 0, len(p))
for field, msg := range p {
msgs = append(msgs, fmt.Sprintf("%s: %s", field, msg))
}
return strings.Join(msgs, "; ")
}


Пример с реальным типом

Допустим, у нас есть запрос на регистрацию периодической задачи:
type TaskCreateRequest struct {
Name string
Run func(ctx context.Context) error
Interval time.Duration
Deadline time.Duration
}


Правила простые: имя обязательно, функция не может быть nil, интервал и дедлайн больше нуля, дедлайн меньше интервала. Реализуем Validate прямо на этом типе:
func (r *TaskCreateRequest) Validate() validation.Problems {
problems := make(validation.Problems)

if r.Name == "" {
problems.Add("Name", "is required")
}
if r.Run == nil {
problems.Add("Run", "is required")
}
if r.Interval <= 0 {
problems.Add("Interval", "must be greater than 0")
}
if r.Deadline <= 0 {
problems.Add("Deadline", "must be greater than 0")
}
if r.Deadline >= r.Interval {
problems.Add("Deadline", "must be less than Interval")
}

return problems
}


Функция `Check` вместо прямого вызова `Validate`

validation.Problems не реализует интерфейс error, поэтому вызывающий код не должен знать об этом типе. Для этого есть функция Check:
func Check(v Validator) error {
if v == nil {
return errors.New("value is nil, cannot validate")
}
problems := v.Validate()
if len(problems) == 0 {
return nil
}
return fmt.Errorf("validation failed for %s: %s",
typeName(v),
problems.String(),
)
}


Использование выглядит так:
func RegisterTask(req *TaskCreateRequest) error {
if err := validation.Check(req); err != nil {
return err
}
// ...
}


Если передать неполный запрос, получим понятное сообщение:
validation failed for TaskCreateRequest: Deadline: must be greater than 0; Name: is required


Где размещать валидацию

В проектах с архитектурой controller/service/repository валидация живёт на уровне сервиса. Сервис проверяет всё, что к нему приходит — будь то HTTP-запрос, gRPC или stdin.

Удобно держать Validate рядом с определением типа. Когда что-то идёт не так — плохие данные прошли до базы или хорошие данные были отклонены — сразу понятно, куда смотреть.

Подход работает за счёт трёх вещей: единый интерфейс Validator, тип Problems для читаемых ошибок и функция Check, которая возвращает стандартный error. Валидация остаётся рядом с типом, код не размазывается по проекту, и добавить новую проверку дело одной строки.

➡️ Оригинал

📍 Навигация: Вакансии • Задачи • Собесы

🐸 Библиотека Go-разработчика

#GoDeep
  • 👍 18
  • ❤ 5
  • 🔥 4
More from @goproglib
  1. Sep 30, 2026🧑‍💻 Эмулятор AWS-сервисов Kumo — это небольшой инструмент на Go для локальной имитации A…
  2. Sep 29, 2026👨‍💻 Библиотека для написания LSP-серверов Написать свой Language Server с нуля на Go сло…
  3. Sep 28, 2026🤔 Вопрос с собеседования по Go Что выведет программа? ❤️ — 1 true / 0 false 🔥 — 1 true /…
  4. Sep 28, 2026👩‍💻 Что на самом деле происходит внутри Go map? После Go 1.24 обычный map внутри работае…
  5. Sep 26, 2026🔥 В Go 1.27 появился portable SIMD До этого SIMD-оптимизации в Go требовали архитектурног…
  6. Sep 25, 2026🤡🤡 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека Go-разработчика #GoGiggle
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →