Валидация данных в Go при помощи govalidator

Валидация уже очень давно применяется в самых различных веб и декстопных приложениях. Уже сломано немало копий, написана куча пакетов для различных языков программирования, решающих эту задачу. Давайте посмотрим, каким образом можно проверять данные в Go программах, используя пакет govalidator.

Валидация

Валидация входных данных - это проверка этих самых данных на соответствие некоторым условиям. “Невалидные” данные, не удовлетворяющие определённым ограничениям или условиям, могут вызывать сбой в программе, порою весьма критичный. Давайте представим, что в каком-то месте выстреливает исключение, например, в момент когда программа пытается преобразовать строку некорректного формата в число. Если это исключение не обрабатывается где-то в программе, то есть вероятность, что произойдет аварийное завершение программы.

И падение программы - это только одна из неприятностей, которые могут произойти. Мы можем перехватывать исключение и восстанавливаться после сбоев. Однако это связанно с накладными расходами, что может ощутимо снизить производительность программы, что тоже не всегда является плюсом.

Кроме того, падения может вообще не произойти, но последствия от этого будут не менее плачевными. Если данные недостаточно проверяются, то это открытая дверь для внешних инъекций. Злоумышленник может получить несанкционированный доступ к данным или некоторым возможностям программы, либо способен вообще разрушить данные или программу.

Когда где-то внутри программы происходит исключительная ситуация, то не всегда есть возможность проследить причины ее возникновения. И даже если это возможно, то нужно как-то объяснить пользователю, что сбой вызван данными, которыми он ввел.

Все это и является причинами, по которым люди придумали валидировать данные, и в случае ошибочных данных немедленно сообщать об этом.

Пакет govalidator

Этот пакет предоставляет простой интерфейс для проверки некоторых строковых данных на соответствие определенным условиям. У всех функций примерно одинаковый формат, вида func IsSomething(str string) bool, причем таких функций достаточно много, почти на все случаи жизни.

Использование

Чтобы начать пользоваться пакетом, его нужно установить в вашем рабочем окружении:

1
go get github.com/asaskevich/govalidator

Теперь мы можем пользоваться подключить пакет govalidator в своем проекте и использовать его.

1
2
3
import (
"github.com/asaskevich/govalidator"
)

Если длинное название пакета вас не устраивает, можно заменить его на более краткое, например так:

1
2
3
import (
v "github.com/asaskevich/govalidator"
)

Функций, доступных к использованию, очень много, большинство из них имеют сигнатуру IsSomething(str string) bool. Кроме того, пакет имеет функции конвертации данных, например преобразование строк в числа и анонимные структуры.

Мы можем валидировать данные по отдельности:

1
fmt.Println(govalidator.IsAlphanumeric("12345"))

либо валидировать сразу целую структуру. Для этого удобно использовать теги valid, указывая таким образом, как необходимо проверить структуру. Если вы хотите применить к одному полю более одного валидатора, то перечисляйте их через запятую. В случае когда вам нужно пропустить валидацию - используйте - в теге valid:

1
2
3
4
5
6
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"duck,ascii"`
AuthorIP string `valid:"ipv4"`
Date string `valid:"-"`
}

Соответствие тегов и методов валидации приведено в этом списке. Если вам чего-то не хватает, то можно добавить свой валидатор к общему списку, устройство пакета приветствует это:

1
2
3
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})

Теперь можем проверить всю структуру сразу:

1
2
3
4
5
6
7
8
9
10
post := &Post{
Title: "My Example Post",
Message: "duck",
AuthorIP: "123.234.54.3",
}
result, err := govalidator.ValidateStruct(post)
if err != nil {
fmt.Println("error: " + err.Error())
}
fmt.Println(result)

Заметим, что пакет проверяет структуры рекурсивно, что еще более упрощает жизнь разработчика.

Как видите, использование специализированных валидаторов упрощает работу с данными.

Устройство

Основную роль в пакете govalidator играют пакеты reflect и regexp. Регулярные выражения нам нужны для сравнения строковых данных с паттернами:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package govalidator

import "regexp"

// Основные регулярные выражения для валидации строк
const (
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[-+]?[0-9]+$"
//...
)

var (
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
//...
)

Все регулярные выражения в пакете сразу компилируются, уменьшая время работы каждого валидатора.

Пакет reflect используется для работы с типами, а также валидации структур. Так же при помощи него мы можем разбирать теги структур, а это не возможно без пакета reflect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ValidateStruct используем теги для полей структуры
func ValidateStruct(s interface{}) (bool, error) {
if s == nil {
return true, nil
}
result := true
var err error
val := reflect.ValueOf(s)
if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr {
val = val.Elem()
}
//...
}
//...

Аналоги

Одним из самых ближайших аналогов является пакет github.com/go-validator/validator. Этот пакет также может валидировать структуры целиком, однако его функционал значительно беднее.

Вероятно, если вы пишете веб приложение, то вам стоит обратить внимания на более веб-ориентированные решения, такие как github.com/absoludity/goforms. Преимущество таких пакетов в том, что они являются надстройкой над http.Request и используются для удобной работы с формами, в том числе и валидации данных из этой формы. Конечно, это значительно менее универсальный подход, который ограничивает область валидации данных.

Аналоги в других языках

Проблема валидации данных не нова, уже существует достаточное множество решений, разработанных с учетом потребностей пользователей, а также языков реализации. Самым ближайшим “родственником” данного пакета является JavaScript пакет validator.js. В языке Java сходную задачу решает BeanValidation.

Отзывы пользователей

Естественно, всегда найдутся критики, которые не только укажут на недостаток, но и помогут его решить. К примеру, некоторые были удивлены тем, что некоторые функции пакета являются просто надстройкой над стандартными функциями Go. Некоторые были удивлены тем, что проверка email адресов производится на основе регулярного выражения. По их мнению, гораздо лучше послать сообщение конечному пользователю, убедившись таким образом не просто в корректности адреса, а в его реальности. Но в целом пакет принят комьюнити и активно им поддерживается, за полгода существования приняты и внесены десятки пулл-реквестов, улучшающих пакет в разы.

Заключение

Валиция всегда была и остается очень важной проблемой, которая должна решаться в рамках любого приложения. К счастью, уже написано множество пакетов, которые выполняют за нас часть работы. Для внедрения максимально надежной, гибкой и универсальной валидации данных можно и нужно использовать пакет govalidator.

Маленькая заметка

Данная статья была первоначально опубликована вот здесь, за что отдельное спасибо Артему Ковардину.