Библиотека валидации Validator
Официальный сайт: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)
Документация: validator/README.md at master · go-playground/validator (github.com)
Официальные примеры: validator/_examples at master · go-playground/validator (github.com)
Введение
go-playground/validator реализует валидатор значений на основе тегов структур со следующими уникальными возможностями:
Можно использовать теги валидации и пользовательские валидаторы для межполевой и межструктурной валидации
Слайсы, массивы, карты или любые многомерные поля могут быть валидированы
Можно погружаться в валидацию ключей и значений карт
Определяет, как обрабатывать перед валидацией по его базовому типу
Может обрабатывать пользовательские типы полей
Поддерживает теги псевдонимов, которые позволяют сопоставить несколько валидаций одному тегу для более простого определения валидации структур
Может извлекать пользовательские имена полей, такие как JSON-имена для отображения в сообщениях об ошибках
Пользовательские многоязычные сообщения об ошибках
Стандартный компонент валидации по умолчанию для фреймворка
gin
Установка
go get github.com/go-playground/validator/v10Импорт
import "github.com/go-playground/validator/v10"Теги
Валидатор имеет множество базовых тегов валидации. Все функции валидации, соответствующие тегам, можно найти в файле baked_in.go. Тег структуры для валидатора — validate.
Например
type User {
age int `validate:"gte=18"` // означает больше или равно 18 годам
}Вы также можете изменить тег по умолчанию через метод setTagName.
Поля
| Tag | Описание |
|---|---|
eqcsfield | В отдельной структуре валидирует, что значение текущего поля равно значению поля, указанного в параметре |
eqfield | Валидирует, что значение текущего поля равно значению поля, указанного в параметре |
fieldcontains | Валидирует, что значение текущего поля содержит значение поля, указанного в параметре |
fieldexcludes | Валидирует, что значение текущего поля не содержит значение поля, указанного в параметре |
gtcsfield | В отдельной структуре валидирует, что значение текущего поля больше значения поля, указанного в параметре |
gtecsfield | В отдельной структуре валидирует, что значение текущего поля больше или равно значению поля, указанного в параметре |
gtefield | Валидирует, что значение текущего поля больше или равно значению поля, указанного в параметре |
gtfield | Валидирует, что значение текущего поля больше значения поля, указанного в параметре |
ltcsfield | В отдельной структуре валидирует, что значение текущего поля меньше значения поля, указанного в параметре |
ltecsfield | В отдельной структуре валидирует, что значение текущего поля меньше или равно значению поля, указанного в параметре |
ltefield | Валидирует, что значение текущего поля меньше или равно значению поля, указанного в параметре |
ltfield | Валидирует, что значение текущего поля меньше значения поля, указанного в параметре |
necsfield | Валидирует, что значение текущего поля не равно значению поля в отдельной структуре, указанному в параметре |
nefield | Валидирует, что значение текущего поля не равно значению поля, указанного в параметре |
Сеть
| Tag | Описание |
|---|---|
cidr | Бесклассовая междоменная маршрутизация CIDR |
cidrv4 | Бесклассовая междоменная маршрутизация CIDRv4 |
cidrv6 | Бесклассовая междоменная маршрутизация CIDRv6 |
datauri | Идентификатор ресурса данных |
fqdn | Полностью квалифицированное доменное имя (FQDN) |
hostname | Имя хоста RFC 952 |
hostname_port | Валидация поля для комбинации <dns>:<port>, обычно используемой для адресов сокетов |
hostname_rfc1123 | Имя хоста RFC 952 |
ip | Адрес интернет-протокола IP |
ip4_addr | Адрес интернет-протокола IPv4 |
ip6_addr | Адрес интернет-протокола IPv6 |
ip_addr | Адрес интернет-протокола IP |
ipv4 | Адрес интернет-протокола IPv4 |
ipv6 | Адрес интернет-протокола IPv6 |
mac | Адрес управления доступом к среде, также известный как LAN-адрес |
tcp4_addr | Адрес протокола управления передачей TCP4 |
tcp6_addr | Адрес протокола управления передачей TCPv6 |
tcp_addr | Адрес протокола управления передачей TCP |
udp4_addr | Адрес пользовательского дейтаграммного протокола UDPv4 |
udp6_addr | Адрес пользовательского дейтаграммного протокола UDPv6 |
udp_addr | Адрес пользовательского дейтаграммного протокола UDP |
unix_addr | Адрес конечной точки доменного сокета Unix |
uri | Идентификатор ресурса Uniform Resource Identifier |
url | Локатор ресурса Uniform Resource Locator |
url_encoded | Кодирование идентификатора ресурса Uniform Resource Identifier encoding |
urn_rfc2141 | Имя ресурса Uniform Resource Name RFC 2141 |
Строки
| Tag | Описание |
|---|---|
alpha | Валидирует, что значение текущего поля является допустимой алфавитной строкой |
alphanum | Валидирует, что значение текущего поля является допустимой алфавитно-цифровой строкой |
alphanumunicode | Валидирует, что значение текущего поля является допустимой алфавитно-цифровой unicode-строкой |
alphaunicode | Валидирует, что значение текущего поля является допустимой алфавитной unicode-строкой |
ascii | Валидирует, что значение поля является допустимыми символами ASCII |
boolean | Валидирует, что значение текущего поля является допустимым булевым или может быть безопасно преобразовано в булево |
contains | Валидирует, что значение поля содержит текст, указанный в параметре |
containsany | Валидирует, что значение поля содержит любой из символов, указанных в параметре |
containsrune | Валидирует, что значение поля содержит руну, указанную в параметре |
endsnotwith | Валидирует, что значение поля не заканчивается текстом, указанным в параметре |
endswith | Валидирует, что значение поля заканчивается текстом, указанным в параметре |
excludes | Валидирует, что значение поля не содержит текст, указанный в параметре |
excludesall | Валидирует, что значение поля не содержит ни одного из символов, указанных в параметре |
excludesrune | Валидирует, что значение поля не содержит руну, указанную в параметре |
lowercase | Валидирует, что значение текущего поля является строкой в нижнем регистре |
multibyte | Валидирует, что значение поля имеет многобайтовые символы |
number | Валидирует, что значение текущего поля является допустимым числом |
numeric | Валидирует, что значение текущего поля является допустимым числовым значением |
printascii | Валидирует, что значение поля является допустимыми печатными символами ASCII |
startsnotwith | Валидирует, что значение поля не начинается с текста, указанного в параметре |
startswith | Валидирует, что значение поля начинается с текста, указанного в параметре |
uppercase | Валидирует, что значение текущего поля является строкой в верхнем регистре |
Форматирование
| Tag | Описание |
|---|---|
base64 | Строка Base64 |
base64url | Строка Base64URL |
bic | Валидирует, что значение текущего поля является допустимым BIC-кодом (SWIFT-кодом) как определено в ISO 9362 |
bcp47_language_tag | Валидирует, что значение текущего поля является языковым тегом BCP47 |
btc_addr | Валидирует, что значение поля является допустимым BTC-адресом |
btc_addr_bech32 | Валидирует, что значение поля является допустимым bech32 BTC-адресом |
credit_card | Валидирует, что значение текущего поля является допустимым номером кредитной карты |
datetime | Валидирует, что значение текущего поля является допустимой строкой datetime |
e164 | Валидирует, что значение текущего поля является допустимым форматом номера телефона e.164 |
email | Валидирует, что значение текущего поля является допустимым адресом электронной почты |
eth_addr | Валидирует, что значение поля является допустимым Ethereum-адресом |
hexadecimal | Валидирует, что значение текущего поля является допустимым шестнадцатеричным |
hexcolor | Валидирует, что значение текущего поля является допустимым hex-цветом |
hsl | Валидирует, что значение текущего поля является допустимым цветом HSL |
hsla | Валидирует, что значение текущего поля является допустимым цветом HSLA |
html | Валидирует, что значение текущего поля является допустимым HTML |
html_encoded | Валидирует, что значение текущего поля является допустимым HTML-кодированным |
isbn | Валидирует, что значение поля является допустимым ISBN v10 или v13 (International Standard Book Number) |
isbn10 | Валидирует, что значение поля является допустимым ISBN v10 |
isbn13 | Валидирует, что значение поля является допустимым ISBN v13 |
iso3166_1_alpha2 | Валидирует, что значение текущего поля является допустимым кодом страны iso3166-1 alpha-2 |
iso3166_1_alpha3 | Валидирует, что значение текущего поля является допустимым кодом страны iso3166-1 alpha-3 |
iso3166_1_alpha_numeric | Валидирует, что значение текущего поля является допустимым alphanumeric-кодом страны iso3166-1 |
iso3166_2 | Валидирует, что значение текущего поля является допустимым кодом региона страны (ISO 3166-2) |
iso4217 | Валидирует, что значение текущего поля является допустимым кодом валюты (ISO 4217) |
json | Валидирует, что значение текущего поля является допустимой строкой JSON |
jwt | Валидирует, что значение текущего поля является допустимой строкой JWT |
latitude | Валидирует, что значение поля является допустимой координатой широты |
longitude | Валидирует, что значение поля является допустимой координатой долготы |
postcode_iso3166_alpha2 | Валидирует по значению кода страны в iso 3166 alpha 2 |
postcode_iso3166_alpha2_field | Валидирует по полю, которое представляет значение кода страны в iso 3166 alpha 2 |
rgb | Валидирует, что значение текущего поля является допустимым цветом RGB |
rgba | Валидирует, что значение текущего поля является допустимым цветом RGBA |
ssn | Валидирует, что значение поля является допустимым SSN |
timezone | Валидирует, что значение текущего поля является допустимой строкой timezone |
uuid | Валидирует, что значение поля является допустимым UUID любой версии |
uuid3 | Валидирует, что значение поля является допустимым UUID v3 |
uuid3_rfc4122 | Валидирует, что значение поля является допустимым RFC4122 v3 UUID |
uuid4 | Валидирует, что значение поля является допустимым v4 UUID |
uuid4_rfc4122 | Валидирует, что значение поля является допустимым RFC4122 v4 UUID |
uuid5 | Валидирует, что значение поля является допустимым v5 UUID |
uuid5_rfc4122 | Валидирует, что значение поля является допустимым RFC4122 v5 UUID |
uuid_rfc4122 | Валидирует, что значение поля является допустимым RFC4122 UUID любой версии |
md4 | Валидирует, что значение поля является допустимым MD4 |
md5 | Валидирует, что значение поля является допустимым MD5 |
sha256 | Валидирует, что значение поля является допустимым SHA256 |
sha384 | Валидирует, что значение поля является допустимым SHA384 |
sha512 | Валидирует, что значение поля является допустимым SHA512 |
ripemd128 | Валидирует, что значение поля является допустимым RIPEMD128 |
ripemd160 | Валидирует, что значение поля является допустимым RIPEMD160 |
tiger128 | Валидирует, что значение поля является допустимым TIGER128 |
tiger160 | Валидирует, что значение поля является допустимым TIGER160 |
tiger192 | Валидирует, что значение поля является допустимым TIGER192 |
semver | Валидирует, что значение текущего поля является допустимой версией semver как определено в Semantic Versioning 2.0.0 |
ulid | Валидирует, что значение поля является допустимым ULID |
Сравнение
| Tag | Описание |
|---|---|
eq | Равно |
gt | Больше чем |
gte | Больше или равно |
lt | Меньше чем |
lte | Меньше или равно |
ne | Не равно |
Другое
| Tag | Описание |
|---|---|
dir | Каталог файлов |
file | Путь к файлу |
isdefault | Валидирует, что значение текущего поля является значением по умолчанию статическим |
len | Длина поля |
max | Максимальное значение |
min | Минимальное значение |
oneof | Является ли одним из перечисленных значений |
omitempty | Если поле не установлено, игнорировать поле |
required | Обязательное значение |
required_if | Поле должно присутствовать и не быть пустым только когда все другие указанные поля равны указанному значению |
required_unless | Поле должно присутствовать и не быть пустым, если только все другие указанные поля не равны указанному значению |
required_with | Поле должно присутствовать и не быть пустым, когда существует любое из указанных полей |
required_with_all | Поле должно присутствовать и не быть пустым, когда существуют все указанные поля |
required_without | Поле должно присутствовать и не быть пустым, когда не существует любое из указанных полей |
required_without_all | Поле должно присутствовать и не быть пустым, когда не существуют все указанные поля |
excluded_if | Поле может отсутствовать или быть пустым только когда все другие указанные поля равны указанному значению |
excluded_unless | Поле может отсутствовать или быть пустым, если только все другие указанные поля не равны указанному значению |
excluded_with | Поле может отсутствовать или быть пустым, когда существует любое из указанных полей |
excluded_with_all | Поле может отсутствовать или быть пустым, когда существуют все указанные поля |
excluded_without | Поле может отсутствовать или быть пустым, когда не существует любое из указанных полей |
excluded_without_all | Поле может отсутствовать или быть пустым, когда не существуют все указанные поля |
unique | Валидирует, что каждое значение arr, map, slice уникально |
Псевдонимы
| Tag | Описание |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
Операторы
| Tag | Описание | Hex |
|---|---|---|
, | Оператор AND, используйте несколько тегов валидации, все условия должны быть выполнены, без пробелов между запятыми | 0x2c |
| | Оператор OR, используйте несколько тегов валидации, но только один должен быть выполнен | 0x7c |
- | Пропустить валидацию для этого поля | 0x2d |
= | Символ сопоставления параметров | 0x3d |
TIP
При сопоставлении операторов во время валидации поля нужно заменить их на шестнадцатеричное представление utf8, например
filed string `validate:"contains=0x2c"`Использование
Далее будет представлено некоторое базовое использование Validator с примерами кода.
Singleton
var validate *validator.ValidateПри использовании официальная рекомендация — иметь только один экземпляр валидатора на протяжении жизненного цикла программы, что поможет кэшировать некоторые данные.
Создание валидатора
При использовании Validator отдельно без интеграции с другими фреймворками, нам нужно вручную создать валидатор.
validate = validator.New()Валидация структур
func (v *Validate) Struct(s interface{}) errorМетод Struct используется для валидации всех публичных полей структуры. По умолчанию он автоматически выполняет вложенную валидацию структур. Когда передаётся недопустимое значение или значение nil, возвращается InvalidValidationError. Если валидация не удалась, возвращается ValidationErrors.
Пример
package validate
import (
"fmt"
"github.com/go-playground/validator/v10"
"testing"
)
type User struct {
Name string `validate:"contains=jack"` // Имя содержит jack
Age int `validate:"gte=18"` // Больше или равно 18 годам
Address string `validate:"endswith=市"` // Заканчивается на 市 (город)
}
func TestStruct(t *testing.T) {
validate := validator.New()
user := User{
Name: "jacklove",
Age: 17,
Address: "滔博市",
}
err := validate.Struct(user)
for _, err := range err.(validator.ValidationErrors) {
fmt.Println(err.Namespace()) // Пространство имён
fmt.Println(err.Field())
fmt.Println(err.StructNamespace())
fmt.Println(err.StructField())
fmt.Println(err.Tag())
fmt.Println(err.ActualTag())
fmt.Println(err.Kind())
fmt.Println(err.Type())
fmt.Println(err.Value())
fmt.Println(err.Param())
fmt.Println()
}
fmt.Println(err)
}Вывод
User.Age
Age
User.Age
Age
gte
gte
int
int
17
18
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tagВалидация карт
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}Валидирует пары ключ-значение через тег map.
Пример
func TestMap(t *testing.T) {
user := map[string]interface{}{
"name": "jak",
"age": 17,
"address": "滔博市",
}
rules := map[string]interface{}{
"name": "contains=jacklove",
"age": "gte=18",
"address": "endswith=市",
}
validate := validator.New()
validateMap := validate.ValidateMap(user, rules)
fmt.Println(validateMap)
}Вывод
map[age:Key: '' Error:Field validation for '' failed on the 'gte' tag name:Key: '' Error:Field validation for '' failed on the 'contains' tag]Валидация слайсов
Валидирует строковые слайсы. Теги до dive валидируют слайс, теги после dive валидируют значения в слайсе. Вложенные слайсы следуют тому же принципу — используйте столько тегов dive, сколько измерений.
func TestSlice1(t *testing.T) {
list := []string{"jack", "mike", "lisa", "golang"}
err := validator.New().Var(list, "max=5,dive,contains=a,min=5") // Максимальная длина слайса 5, элементы должны содержать символ 'a', и минимальная длина 5
fmt.Println(err)
}Вывод
Key: '[0]' Error:Field validation for '[0]' failed on the 'min' tag
Key: '[1]' Error:Field validation for '[1]' failed on the 'contains' tag
Key: '[2]' Error:Field validation for '[2]' failed on the 'min' tagВалидировать каждого пользователя в слайсе со валидацией структур
func TestSlice(t *testing.T) {
userList := make([]User, 0)
user := User{
Name: "jacklove",
Age: 17,
Address: "滔博市",
}
userList = append(userList, user)
err := validator.New().Var(userList, "dive") // "dive" означает глубокую валидацию, автоматически выполняет валидацию структур, когда элемент является структурой
fmt.Println(err)
}Вывод
Key: '[0].Age' Error:Field validation for 'Age' failed on the 'gte' tagВалидация переменных
Просто и легко понять, не требует чрезмерного объяснения
Пример 1
func TestVar(t *testing.T) {
name := "jack"
err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") // Максимальная длина 5, минимальная длина 1, содержит букву 'a', заканчивается на букву 'l'
fmt.Println(err)
}Вывод
Key: '' Error:Field validation for '' failed on the 'endswith' tagПример 2
func TestVar1(t *testing.T) {
age := 18
err := validator.New().Var(age, "gte=19")
fmt.Println(err)
}Вывод
Key: '' Error:Field validation for '' failed on the 'gte' tagTIP
Метод Var может валидировать типы включая структуры, переменные, слайсы и карты. Используйте его разумно в сочетании с тегом dive.
Валидация полей
Параметры валидации полей больше не являются базовыми типами, а именами полей структур. Они могут быть именами полей самой структуры или именами полей вложенных структур.
type Password struct {
FirstPassword string `validate:"eqfield=SecondPassword"` // Валидирует, если два ввода пароля равны
SecondPassword string
}
type RegisterUser struct {
Username string `validate:"necsfield=Password.FirstPassword"` // Для безопасности во время регистрации запретить пароль, совпадающий с именем пользователя
Password Password
}
func TestCrossStructFieldValidate(t *testing.T) {
validate = validator.New()
// Неудача
fmt.Println(validate.Struct(RegisterUser{
Username: "gopher",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gophers",
},
}))
// Успех
fmt.Println(validate.Struct(RegisterUser{
Username: "gophers",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gopher",
},
}))
}Вывод
Key: 'RegisterUser.Username' Error:Field validation for 'Username' failed on the 'necsfield' tag
Key: 'RegisterUser.Password.FirstPassword' Error:Field validation for 'FirstPassword' failed on the 'eqfield' tag
<nil>WARNING
При использовании валидации полей, если поле или структура, указанная как параметр Tag, не существует, она будет напрямую признана как неудача валидации. Например:
type Password struct {
FirstPassword string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
SecondPassword string
}Для таких ошибок написания трудно обнаружить, и во время валидации это покажет только как неудача валидации. Это требует особого внимания.
Продвинутое использование
Далее мы объясним некоторые продвинутые приёмы использования и больше пользовательских операций.
Пользовательские псевдонимы
Иногда у поля много тегов валидации. Когда вы хотите переиспользовать их на другом поле, вы можете просто скопировать и вставить, но это не лучшее решение. Лучший подход — зарегистрировать псевдонимы для улучшения переиспользования. См. пример ниже:
var validate *validator.Validate
const PERSON_NAME_RULES = "max=10,min=1,contains=jack"
func TestAlias(t *testing.T) {
validate = validator.New()
// Зарегистрировать псевдоним
validate.RegisterAlias("namerules", PERSON_NAME_RULES)
type person struct {
FirstName string `validate:"namerules"` // Использовать псевдоним
LastName string `validate:"namerules"`
}
err := validate.Struct(person{
FirstName: "",
LastName: "",
})
fmt.Println(err)
}Вывод
Key: 'person.FirstName' Error:Field validation for 'FirstName' failed on the 'namerules' tag
Key: 'person.LastName' Error:Field validation for 'LastName' failed on the 'namerules' tagПользовательские функции валидации
Хотя встроенные теги валидации компонента достаточны для базовых сценариев, иногда для специальных требований вы должны определить свою собственную логику. Validator предоставляет связанные API для кастомизации функций валидации. Рассмотрим пример:
func TestCustomValidate(t *testing.T) {
validate = validator.New()
fmt.Println(validate.RegisterValidation("is666", is666))
type Example struct {
Name string `validate:"is666"`
}
fmt.Println(validate.Struct(Example{Name: "777"}))
fmt.Println(validate.Struct(Example{Name: "666"}))
}
func is666(fl validator.FieldLevel) bool {
return fl.Field().String() == "666"
}Создана функция для проверки, равно ли значение поля "666", и её соответствующий тег — is666. Вывод следующий:
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tagTIP
Если зарегистрированный тег уже существует, он будет перезаписан существующим. Другими словами, вы можете "переписать" логику валидации тега по умолчанию.
Пользовательские функции валидации типов
Функции валидации типов специально предназначены для определённых типов, обычно для небазовых типов. Аналогично, вы также можете переопределить валидацию базового типа по умолчанию. См. пример ниже:
type Address struct {
name string
}
func TestCustomTypeValidate(t *testing.T) {
validate = validator.New()
validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // Зарегистрировать функцию валидации типа и соответствующий тип
type Example struct {
Address Address `validate:"required"`
}
fmt.Println(validate.Struct(Example{Address: Address{name: ""}}))
fmt.Println(validate.Struct(Example{Address: Address{name: "cn"}}))
}
func ValidateAddress(value reflect.Value) interface{} {
if address, ok := value.Interface().(Address); ok {
// Обработка ошибок
if address.name == "" {
return address.name
}
return value // Возврат поля означает, что валидация пройдена
}
return nil
}Вывод
Key: 'Example.Address' Error:Field validation for 'Address' failed on the 'required' tag
<nil>TIP
Регистрация нескольких типов в одну функцию следует тому же принципу.
Пользовательские функции валидации уровня структур
Отличие от функций валидации уровня структур в том, что параметры других функций — это поля, а параметр этой функции — сама структура. См. пример ниже:
type People struct {
FirstName string
LastName string
}
func TestCustomStructLevel(t *testing.T) {
validate = validator.New()
validate.RegisterStructValidation(PeopleValidate, People{}) // Как и регистрация типа, вы можете передать более одного типа структуры
err := validate.Struct(People{
FirstName: "",
LastName: "",
})
fmt.Println(err)
}
func PeopleValidate(sl validator.StructLevel) {
people := sl.Current().Interface().(People)
if people.FirstName == "" || people.LastName == "" {
sl.ReportError(people.FirstName, "FirstName", "FirstName", "", "")
sl.ReportError(people.FirstName, "LastName", "LastName", "", "")
}
}Вывод
Key: 'People.FirstName' Error:Field validation for 'FirstName' failed on the '' tag
Key: 'People.LastName' Error:Field validation for 'LastName' failed on the '' tagМногоязычность
Компонент переводчика
go get github.com/go-playground/universal-translatorКомпонент локали
go get github.com/go-playground/localesЯзык валидатора по умолчанию — английский. При разработке проектов нам может потребоваться использовать более одного языка. В этом случае нам нужно использовать компонент интернационализации многоязычности. См. пример ниже:
import (
"fmt"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_trans "github.com/go-playground/validator/v10/translations/zh"
"reflect"
"testing"
)
type User struct {
Name string `validate:"contains=jack"` // Имя содержит jack
Age int `validate:"gte=18"` // Больше или равно 18 годам
Address string `validate:"endswith=市"` // Заканчивается на 市 (город)
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
func TestTranslate(t *testing.T) {
zh := zh.New()
// Первый — резервный, последующие — поддерживаемые языки, может быть несколько
uni = ut.New(zh, zh)
// Язык здесь обычно получается из заголовка Accept-Language в HTTP-запросах
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // Зарегистрировать переводчик по умолчанию
}
err := validate.Struct(User{
Name: "",
Age: 0,
Address: "",
})
fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}Вывод
map[User.Address:Address must end with text '市' User.Age:Age must be greater than or equal to 18 User.Name:Name must contain text 'jack']Вы также можете перевести каждую ошибку индивидуально
for _, fieldError := range err.(validator.ValidationErrors) {
fmt.Println(fieldError.Translate(trans))
}Вывод
Name must contain text 'jack'
Age must be greater than or equal to 18
Address must end with text '市'Можно видеть, что возвращаемое значение — это map. Базовый перевод сообщений об ошибках достигнут, но ещё не готово для использования в продакшене. Нам нужно дополнительно улучшить сообщения об ошибках для лучшей интеграции с клиентами или фронтендом.
type User struct {
Name string `validate:"contains=jack" label:"姓名"` // Имя содержит jack
Age int `validate:"gte=18" label:"年龄"` // Больше или равно 18 годам
Address string `validate:"endswith=市" label:"地址"` // Заканчивается на 市 (город)
Sex string `validate:"required" label:"性别"`
}Сначала кастомизируйте тег label. Его значение — китайское имя поля. Затем зарегистрируйте TagNameFunc в валидаторе. Его роль — получать имя поля или заменять оригинальное имя. Комментарий к методу Field() string в файле errors.go гласит: "Имя поля с тегом label имеет приоритет над фактическим именем поля". Поэтому при возникновении ошибок мы можем использовать пользовательское китайское имя для замены английского слова. TagNameFunc следующий:
// Мы добавили пользовательский тег, который используется для предоставления полям структур китайских имён, заменяющих оригинальные имена полей
func CustomTagNameFunc(field reflect.StructField) string {
label := field.Tag.Get("label")
if len(label) == 0 {
return field.Name
}
return label
}Затем зарегистрируйте
validate.RegisterTagNameFunc(CustomTagNameFunc)Выполните снова, вывод
姓名 must contain text 'jack'
年龄 must be greater than or equal to 18
地址 must end with text '市'Но этого ещё недостаточно. Ещё не подходит как информация об ошибках, возвращаемая фронтенду. Нам нужно отформатировать информацию в JSON или любой формат, подходящий для передачи сообщений. Вы можете подумать о прямой сериализации map в JSON, это решение, но вы можете получить следующий результат:
{
"User.地址": "地址 must end with text '市'",
"User.姓名": "姓名 must contain text 'back'",
"User.年龄": "年龄 must be greater than or equal to 18",
"User.性别": "性别 is required"
}Обработав значения ключей map, получите следующий результат:
{
"地址": "地址 must end with text '市'",
"姓名": "姓名 must contain text 'back'",
"年龄": "年龄 must be greater than or equal to 18",
"性别": "性别 is required"
}Однако не рекомендуется возвращать такую информацию фронтенду. Мы можем обработать её в строку как сообщение возврата:
姓名 must contain text 'back', 年龄 must be greater than or equal to 18, 地址 must end with text '市', 性别 is required,Полный код
import (
"fmt"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_trans "github.com/go-playground/validator/v10/translations/zh"
"reflect"
"strings"
"testing"
)
type User struct {
Name string `validate:"contains=back" label:"姓名"` // Имя содержит jack
Age int `validate:"gte=18" label:"年龄"` // Больше или равно 18 годам
Address string `validate:"endswith=市" label:"地址"` // Заканчивается на 市 (город)
Sex string `validate:"required" label:"性别"`
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
// Мы добавили пользовательский тег, который используется для предоставления полям структур китайских имён, заменяющих оригинальные имена полей
func CustomTagNameFunc(field reflect.StructField) string {
label := field.Tag.Get("label")
if len(label) == 0 {
return field.Name
}
return label
}
func TestTranslate(t *testing.T) {
zh := zh.New()
uni = ut.New(zh, zh)
// Язык здесь обычно получается из заголовка Accept-Language в HTTP-запросах
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // Зарегистрировать переводчик по умолчанию
}
validate.RegisterTagNameFunc(CustomTagNameFunc)
err := validate.Struct(User{
Name: "",
Age: 0,
Address: "",
})
translate := errInfoFormat(err.(validator.ValidationErrors), trans)
fmt.Println(translate)
}
func errInfoFormat(errors validator.ValidationErrors, trans ut.Translator) string {
builder := strings.Builder{}
for _, err := range errors {
builder.WriteString(err.Translate(trans))
builder.WriteString(", ")
}
return builder.String()
}Наконец, если вы считаете, что сообщения об ошибках слишком холодные и хотите их более гуманизированными, вы можете переписать сообщения об ошибках для определённых тегов. Это требует использования метода RegisterTranslation, а также требует двух типов функций: RegisterTranslationsFunc, отвечающей за регистрацию шаблона перевода для соответствующего тега, и TranslationFunc, отвечающей за обработку шаблона для получения финального содержимого перевода. Вот пример с использованием required:
func requiredOverrideRegister(ut ut.Translator) error { // Роль этой функции — зарегистрировать шаблон перевода
return ut.Add("required", "{} is a required field", true) // {} — заполнитель, true представляет, переопределять ли существующий шаблон
}
func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // Роль этой функции — перевод содержимого
t, _ := ut.T("required", fe.Field()) // Параметры могут быть множественными, в зависимости от того, сколько заполнителей имеет шаблон зарегистрированного тега
return t
}Наконец зарегистрируйте
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)Результат
姓名 must contain text 'back', 年龄 must be greater than or equal to 18, 地址 must end with text '市', 性别 is a required field,Языковые файлы
Фактически, регистрировать один за другим с помощью кода очень утомительно. universal-translator предоставляет способ перевода через файлы конфигурации JSON: universal-translator/examples/full-with-files at master · go-playground/universal-translator (github.com)
func TestFilei18n(t *testing.T) {
validate = validator.New()
zh := zh.New()
universalTranslator := ut.New(zh, zh)
translator, _ := universalTranslator.GetTranslator(zh.Locale())
zh_trans.RegisterDefaultTranslations(validate, translator)
er := universalTranslator.Import(ut.FormatJSON, "./zh.json") // Рекомендуется импортировать после регистрации, чтобы можно было переопределить существующие теги
if er != nil {
log.Fatal(er)
}
type Gopher struct {
Language string `validate:"required"`
}
err := validate.Struct(Gopher{
"",
})
fmt.Println(err.(validator.ValidationErrors).Translate(translator))
}JSON файл
[
{
"locale": "zh",
"key": "required",
"trans": "Это очень важное поле {0}, вы должны его заполнить",
"override": true
}
]Вывод
map[Gopher.Language:Это очень важное поле Language, вы должны его заполнить]TIP
universal-translator имеет много подводных камней при использовании. Если вы хотите переопределить существующий Tag, type и rule могут быть оставлены пустыми, потому что оригинальная конфигурация также не имеет их. Лучше всего сохранять согласованность. Whatever type вы заполните, будет добавлено в соответствующую map. Если это Cardinal или другой type и rule настроен с one или подобным, тогда вам нужно сделать соответствующие конфигурации локально для нормального использования, иначе это сообщит об ошибке.
