Validator Biblioteca de Validação
Endereço oficial: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)
Endereço da documentação: validator/README.md at master · go-playground/validator (github.com)
Exemplos oficiais: validator/_examples at master · go-playground/validator (github.com)
Introdução
go-playground/validator implementa um validador de valores baseado em tags de struct, com as seguintes características únicas:
- Pode usar tags de validação e validadores customizados para validação entre campos e entre structs
- Slice, array, map ou qualquer campo multidimensional pode ser validado
- Pode validar profundamente chaves e valores de map
- Determina como processar antes da validação através de seu tipo básico
- Pode lidar com tipos de campos customizados
- Suporta tags de alias, permitindo mapear múltiplas validações para uma única tag, facilitando a definição de validações para structs
- Pode extrair nomes de campos customizados, como nomes JSON para exibição em mensagens de erro
- Mensagens de erro multi-idiomas customizadas
- Componente de validação padrão do framework
gin
Instalação
go get github.com/go-playground/validator/v10Importação
import "github.com/go-playground/validator/v10"Tags
O validador possui muitas tags de validação básicas. Todas as funções de validação correspondentes às tags podem ser encontradas no arquivo baked_in.go. A tag de struct do validador é validate.
Por exemplo:
type User {
age int `validate:"gte=18"` // indica maior ou igual a 18 anos
}Também é possível modificar a tag padrão através do método setTagName.
Campos
| Tag | Descrição |
|---|---|
eqcsfield | Em uma struct separada, valida se o valor do campo atual é igual ao campo especificado pelo valor do parâmetro |
eqfield | Valida se o valor do campo atual é igual ao campo especificado pelo valor do parâmetro |
fieldcontains | Valida se o valor do campo atual contém o campo especificado pelo valor do parâmetro |
fieldexcludes | Valida se o valor do campo atual não contém o campo especificado pelo valor do parâmetro |
gtcsfield | Em uma struct separada, valida se o valor do campo atual é maior que o campo especificado pelo valor do parâmetro |
gtecsfield | Em uma struct separada, valida se o valor do campo atual é maior ou igual ao campo especificado pelo valor do parâmetro |
gtefield | Valida se o valor do campo atual é maior ou igual ao campo especificado pelo valor do parâmetro |
gtfield | Valida se o valor do campo atual é maior que o campo especificado pelo valor do parâmetro |
ltcsfield | Em uma struct separada, valida se o valor do campo atual é menor que o campo especificado pelo valor do parâmetro |
ltecsfield | Em uma struct separada, valida se o valor do campo atual é menor ou igual ao campo especificado pelo valor do parâmetro |
ltefield | Valida se o valor do campo atual é menor ou igual ao campo especificado pelo valor do parâmetro |
ltfield | Valida se o valor do campo atual é menor que o campo especificado pelo valor do parâmetro |
necsfield | Valida se o valor do campo atual não é igual ao campo em uma struct separada especificado pelo valor do parâmetro |
nefield | Valida se o valor do campo atual não é igual ao campo especificado pelo valor do parâmetro |
Rede
| Tag | Descrição |
|---|---|
cidr | Classless Inter-Domain Routing CIDR |
cidrv4 | Classless Inter-Domain Routing CIDRv4 |
cidrv6 | Classless Inter-Domain Routing CIDRv6 |
datauri | Data Uniform Resource Locator |
fqdn | Fully Qualified Domain Name (FQDN) |
hostname | Hostname RFC 952 |
hostname_port | Validação de campo de combinação <dns>:<port> comumente usada para endereços de socket |
hostname_rfc1123 | Hostname RFC 952 |
ip | Internet Protocol Address IP |
ip4_addr | Internet Protocol Address IPv4 |
ip6_addr | Internet Protocol Address IPv6 |
ip_addr | Internet Protocol Address IP |
ipv4 | Internet Protocol Address IPv4 |
ipv6 | Internet Protocol Address IPv6 |
mac | Media Access Control Address, também conhecido como endereço LAN |
tcp4_addr | Transmission Control Protocol Address TCP4 |
tcp6_addr | Transmission Control Protocol Address TCPv6 |
tcp_addr | Transmission Control Protocol Address TCP |
udp4_addr | User Datagram Protocol Address UDPv4 |
udp6_addr | User Datagram Protocol Address UDPv6 |
udp_addr | User Datagram Protocol Address UDP |
unix_addr | Unix Domain Socket Endpoint Address |
uri | Uniform Resource Identifier |
url | Uniform Resource Locator |
url_encoded | Uniform Resource Identifier Encoding |
urn_rfc2141 | RFC 2141 Uniform Resource Name |
Strings
| Tag | Descrição |
|---|---|
alpha | Valida se o valor do campo atual é uma letra válida |
alphanum | Valida se o valor do campo atual é alfanumérico válido |
alphanumunicode | Valida se o valor do campo atual é um valor alfanumérico unicode válido |
alphaunicode | Valida se o valor do campo atual é um valor de letra unicode válido |
ascii | Valida se o valor do campo é um caractere ASCII válido |
boolean | Valida se o valor do campo atual é um valor booleano válido ou pode ser convertido com segurança para booleano |
contains | Valida se o valor do campo contém o texto especificado no parâmetro |
containsany | Valida se o valor do campo contém qualquer caractere especificado no parâmetro |
containsrune | Valida se o valor do campo contém o rune especificado no parâmetro |
endsnotwith | Valida se o valor do campo não termina com o texto especificado no parâmetro |
endswith | Valida se o valor do campo termina com o texto especificado no parâmetro |
excludes | Valida se o valor do campo não contém o texto especificado no parâmetro |
excludesall | Valida se o valor do campo não contém nenhum dos caracteres especificados no parâmetro |
excludesrune | Valida se o valor do campo não contém o caractere especificado no parâmetro |
lowercase | Valida se o valor do campo atual é uma string em minúsculas |
multibyte | Valida se o valor do campo possui caracteres multibyte |
number | Valida se o valor do campo atual é um número válido |
numeric | Valida se o valor do campo atual é um valor numérico válido |
printascii | Valida se o valor do campo é um caractere ASCII imprimível válido |
startsnotwith | Valida se o valor do campo não começa com o texto especificado no parâmetro |
startswith | Valida se o valor do campo começa com o texto especificado no parâmetro |
uppercase | Valida se o valor do campo atual é uma string em maiúsculas |
Formatação
| Tag | Descrição |
|---|---|
base64 | String Base64 |
base64url | String Base64URL |
bic | Valida se o valor do campo atual é um código BIC válido conforme definido no ISO 9362 (código SWIFT) |
bcp47_language_tag | Valida se o valor do campo atual é uma tag de idioma da especificação BCP47 |
btc_addr | Valida se o valor do campo é um endereço BTC válido |
btc_addr_bech32 | Valida se o valor do campo é um endereço bech32 BTC válido |
credit_card | Valida se o valor do campo atual é um número de cartão de crédito válido |
datetime | Valida se o valor do campo atual é uma string de data/hora válida |
e164 | Valida se o valor do campo atual é um número de telefone no formato e.164 válido |
email | Valida se o valor do campo atual é um endereço de e-mail válido |
eth_addr | Valida se o valor do campo é um endereço Ethereum válido |
hexadecimal | Valida se o valor do campo atual é um hexadecimal válido |
hexcolor | Valida se o valor do campo atual é uma cor hexadecimal válida |
hsl | Valida se o valor do campo atual é uma cor HSL válida |
hsla | Valida se o valor do campo atual é uma cor HSLA válida |
html | Valida se o valor do campo atual é um HTML válido |
html_encoded | Valida se o valor do campo atual é uma codificação HTML válida |
isbn | Valida se o valor do campo é um ISBN v10 ou v13 válido (International Standard Book Number) |
isbn10 | Valida se o valor do campo é um ISBN v10 válido (International Standard Book Number) |
isbn13 | Valida se o valor do campo é um ISBN v13 válido (International Standard Book Number) |
iso3166_1_alpha2 | Valida se o valor do campo atual é um código de país iso3166-1 alpha-2 válido |
iso3166_1_alpha3 | Valida se o valor do campo atual é um código de país iso3166-1 alpha-3 válido |
iso3166_1_alpha_numeric | Valida se o valor do campo atual é um código de país alfanumérico iso3166-1 válido |
iso3166_2 | Valida se o valor do campo atual é um código de região de país válido (ISO 3166-2) |
iso4217 | Valida se o valor do campo atual é um código de moeda válido (ISO 4217) |
json | Valida se o valor do campo atual é uma string json válida |
jwt | Valida se o valor do campo atual é uma string JWT válida |
latitude | Valida se o valor do campo é uma coordenada de latitude válida |
longitude | Valida se o valor do campo é uma coordenada de longitude válida |
postcode_iso3166_alpha2 | Valida com base no valor do código de país iso 3166 alpha 2 |
postcode_iso3166_alpha2_field | Valida através de campo, onde o campo indica o valor do código de país iso 3166 alpha 2 |
rgb | Valida se o valor do campo atual é uma cor RGB válida |
rgba | Valida se o valor do campo atual é uma cor RGBA válida |
ssn | Valida se o valor do campo é um SSN válido |
timezone | Valida se o valor do campo atual é uma string de fuso horário válida |
uuid | Valida se o valor do campo é um UUID válido de qualquer versão |
uuid3 | Valida se o valor do campo é um UUID v3 válido |
uuid3_rfc4122 | Valida se o valor do campo é um UUID v3 RFC4122 válido |
uuid4 | Valida se o valor do campo é um UUID v4 válido |
uuid4_rfc4122 | Valida se o valor do campo é um UUID v4 RFC4122 válido |
uuid5 | Valida se o valor do campo é um UUID v5 válido |
uuid5_rfc4122 | Valida se o valor do campo é um UUID v5 RFC4122 válido |
uuid_rfc4122 | Valida se o valor do campo é um UUID RFC4122 válido de qualquer versão |
md4 | Valida se o valor do campo é um MD4 válido |
md5 | Valida se o valor do campo é um MD5 válido |
sha256 | Valida se o valor do campo é um SHA256 válido |
sha384 | Valida se o valor do campo é um SHA384 válido |
sha512 | Valida se o valor do campo é um SHA512 válido |
ripemd128 | Valida se o valor do campo é um RIPEMD128 válido |
ripemd160 | Valida se o valor do campo é um RIPEMD160 válido |
tiger128 | Valida se o valor do campo é um TIGER128 válido |
tiger160 | Valida se o valor do campo é um TIGER160 válido |
tiger192 | Valida se o valor do campo é um TIGER192 válido |
semver | Valida se o valor do campo atual é uma versão semver válida conforme definido no Semantic Versioning 2.0.0 |
ulid | Valida se o valor do campo é um ULID válido |
Comparação
| Tag | Descrição |
|---|---|
eq | Igual a |
gt | Maior que |
gte | Maior ou igual a |
lt | Menor que |
lte | Menor ou igual a |
ne | Diferente de |
Outros
| Tag | Descrição |
|---|---|
dir | Diretório de arquivo |
file | Caminho do arquivo |
isdefault | Valida se o valor do campo atual é o valor estático padrão |
len | Comprimento do campo |
max | Valor máximo |
min | Valor mínimo |
oneof | Se é um dos valores listados |
omitempty | Se o campo não estiver definido, ignora o campo |
required | Valor obrigatório |
required_if | Valida que o campo deve existir e não estar vazio apenas quando todos os outros campos especificados são iguais ao valor especificado |
required_unless | Valida que o campo deve existir e não estar vazio a menos que todos os outros campos especificados sejam iguais ao valor especificado |
required_with | Valida que o campo deve existir e não estar vazio quando qualquer um dos campos especificados existe |
required_with_all | Valida que o campo deve existir e não estar vazio quando todos os campos especificados existem |
required_without | Valida que o campo deve existir e não estar vazio quando qualquer um dos campos especificados não existe |
required_without_all | Valida que o campo deve existir e não estar vazio quando todos os campos especificados não existem |
excluded_if | Valida que o campo pode não existir ou estar vazio apenas quando todos os outros campos especificados são iguais ao valor especificado |
excluded_unless | Valida que o campo pode não existir ou estar vazio a menos que todos os outros campos especificados sejam iguais ao valor especificado |
excluded_with | Valida que o campo pode não existir ou estar vazio quando qualquer um dos campos especificados existe |
excluded_with_all | Valida que o campo pode não existir ou estar vazio quando todos os campos especificados existem |
excluded_without | Valida que o campo pode não existir ou estar vazio quando qualquer um dos campos especificados não existe |
excluded_without_all | Valida que o campo pode não existir ou estar vazio quando todos os campos especificados não existem |
unique | Valida se cada valor de arr, map, slice é único |
Alias
| Tag | Descrição |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
Operadores
| Tag | Descrição | Hex |
|---|---|---|
, | Operador E, usa múltiplas tags de validação, todas as condições devem ser satisfeitas, sem espaços entre vírgulas | 0x2c |
| | Operador OU, usa múltiplas tags de validação, mas apenas uma precisa ser satisfeita | 0x7c |
- | Pula a validação para este campo | 0x2d |
= | Símbolo de correspondência de parâmetro | 0x3d |
TIP
Ao validar campos, para corresponder aos operadores, é necessário usar a representação hexadecimal utf8. Por exemplo:
filed string `validate:"contains=0x2c"`Uso
Abaixo serão apresentados alguns usos básicos do Validator com exemplos de código.
Singleton
var validate *validator.ValidateAo usar, é recomendado pela equipe oficial que exista apenas uma instância do validador durante todo o ciclo de vida do programa, o que ajudará a armazenar alguns dados em cache.
Criar Validador
Ao usar o Validator isoladamente sem integração com outros frameworks, é necessário criar o validador manualmente.
validate = validator.New()Validação de Struct
func (v *Validate) Struct(s interface{}) errorO método Struct é usado para validar todos os campos públicos de uma struct. Por padrão, realiza validação aninhada de structs automaticamente. Quando um valor inválido é passado ou o valor é nil, retorna InvalidValidationError. Se a validação falhar, retorna ValidationErrors.
Exemplo:
package validate
import (
"fmt"
"github.com/go-playground/validator/v10"
"testing"
)
type User struct {
Name string `validate:"contains=jack"` // nome contém jack
Age int `validate:"gte=18"` // maior ou igual a 18 anos
Address string `validate:"endswith=市"` // termina com 市
}
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()) // 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)
}Saída:
User.Age
Age
User.Age
Age
gte
gte
int
int
17
18
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tagValidação de Map
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}Validação de pares chave-valor através da tag map.
Exemplo:
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)
}Saída:
map[age:Key: '' Error:Field validation for '' failed on the 'gte' tag name:Key: '' Error:Field validation for '' failed on the 'contains' tag]Validação de Slice
Para validar slice de strings, a tag antes de dive valida o slice, a tag após dive valida os valores no slice. O mesmo princípio se aplica a slices aninhados, use quantos dive forem necessários para as dimensões.
func TestSlice1(t *testing.T) {
list := []string{"jack", "mike", "lisa", "golang"}
err := validator.New().Var(list, "max=5,dive,contains=a,min=5") // comprimento máximo do slice é 5, elementos devem conter caractere a, e comprimento mínimo é 5
fmt.Println(err)
}Saída:
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' tagValidação de struct para cada usuário no slice:
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" significa validação profunda, quando o elemento é uma struct, automaticamente realiza validação de struct
fmt.Println(err)
}Saída:
Key: '[0].Age' Error:Field validation for 'Age' failed on the 'gte' tagValidação de Variável
Simples e fácil de entender, não requer muita explicação.
Exemplo 1:
func TestVar(t *testing.T) {
name := "jack"
err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") // comprimento máximo 5, mínimo 1, contém letra a, termina com letra l
fmt.Println(err)
}Saída:
Key: '' Error:Field validation for '' failed on the 'endswith' tagExemplo 2:
func TestVar1(t *testing.T) {
age := 18
err := validator.New().Var(age, "gte=19")
fmt.Println(err)
}Saída:
Key: '' Error:Field validation for '' failed on the 'gte' tagTIP
O método Var pode validar tipos incluindo struct, variável, slice, map. Deve ser usado adequadamente em combinação com a tag dive.
Validação de Campo
Os parâmetros de validação de campo não são mais tipos básicos, mas nomes de campos de struct, podendo ser campos da própria struct ou campos de structs aninhados.
type Password struct {
FirstPassword string `validate:"eqfield=SecondPassword"` // valida se as duas senhas inseridas são iguais
SecondPassword string
}
type RegisterUser struct {
Username string `validate:"necsfield=Password.FirstPassword"` // no registro, por segurança, proíbe que a senha seja igual ao nome de usuário
Password Password
}
func TestCrossStructFieldValidate(t *testing.T) {
validate = validator.New()
// Falha
fmt.Println(validate.Struct(RegisterUser{
Username: "gopher",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gophers",
},
}))
// Sucesso
fmt.Println(validate.Struct(RegisterUser{
Username: "gophers",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gopher",
},
}))
}Saída:
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
Ao usar validação de campo, quando o campo ou struct especificado como parâmetro na Tag não existe, será considerado como falha de validação. Por exemplo:
type Password struct {
FirstPassword string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
SecondPassword string
}Para erros de digitação como este, é difícil detectar, e durante a validação será apenas exibido como não aprovado. É necessário ter muita atenção.
Avançado
A seguir serão explicadas técnicas de uso avançado e mais operações customizadas.
Alias Customizado
Às vezes, um campo possui muitas tags de validação. Quando você deseja reutilizar em outro campo, pode simplesmente copiar e colar, mas esta não é a melhor solução. O melhor método é registrar um alias para melhorar a reutilização. Veja o exemplo abaixo:
var validate *validator.Validate
const PERSON_NAME_RULES = "max=10,min=1,contains=jack"
func TestAlias(t *testing.T) {
validate = validator.New()
// Registra alias
validate.RegisterAlias("namerules", PERSON_NAME_RULES)
type person struct {
FirstName string `validate:"namerules"` // usa alias
LastName string `validate:"namerules"`
}
err := validate.Struct(person{
FirstName: "",
LastName: "",
})
fmt.Println(err)
}Saída:
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' tagFunção de Validação Customizada
Embora as tags de validação fornecidas pelo componente sejam suficientes para a maioria dos casos, às vezes é necessário definir lógica customizada para necessidades especiais. Validator fornece APIs para customizar funções de validação. Veja o exemplo abaixo:
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"
}Cria uma função que verifica se o valor do campo é igual a "666", e sua tag correspondente é is666. Saída:
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tagTIP
Se a tag registrada já existir, ela será sobrescrita pela atual. Em outras palavras, é possível "reescrever" a lógica de validação padrão da tag.
Função de Validação de Tipo Customizada
A função de validação de tipo é específica para um determinado tipo, geralmente usada para tipos não básicos. Também é possível sobrescrever a validação de tipos básicos padrão. Veja o exemplo abaixo:
type Address struct {
name string
}
func TestCustomTypeValidate(t *testing.T) {
validate = validator.New()
validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // registra função de validação de tipo e o tipo correspondente
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 {
// tratamento de erro
if address.name == "" {
return address.name
}
return value // retorna o campo, representa que a validação foi bem-sucedida
}
return nil
}Saída:
Key: 'Example.Address' Error:Field validation for 'Address' failed on the 'required' tag
<nil>TIP
Registrar múltiplos tipos em uma função também funciona da mesma maneira.
Função de Validação de Struct Customizada
A diferença da função de validação de struct é que os parâmetros das outras funções são campos, enquanto esta função recebe a struct. Veja o exemplo abaixo:
type People struct {
FirstName string
LastName string
}
func TestCustomStructLevel(t *testing.T) {
validate = validator.New()
validate.RegisterStructValidation(PeopleValidate, People{}) // assim como registro de tipo, é possível passar mais de um tipo de struct
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", "", "")
}
}Saída:
Key: 'People.FirstName' Error:Field validation for 'FirstName' failed on the '' tag
Key: 'People.LastName' Error:Field validation for 'LastName' failed on the '' tagMulti-idiomas
Componente tradutor:
go get github.com/go-playground/universal-translatorComponente de região:
go get github.com/go-playground/localesO idioma padrão do validador é inglês. Ao desenvolver projetos, pode ser necessário usar mais de um idioma. Neste caso, precisamos usar o componente de internacionalização. Veja o exemplo abaixo:
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"` // nome contém jack
Age int `validate:"gte=18"` // maior ou igual a 18 anos
Address string `validate:"endswith=市"` // termina com 市
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
func TestTranslate(t *testing.T) {
zh := zh.New()
// o primeiro é reserva, os seguintes são idiomas suportados, pode haver múltiplos
uni = ut.New(zh, zh)
// o idioma aqui geralmente pode ser obtido do cabeçalho Accept-Language da requisição http
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // registra tradutor padrão
}
err := validate.Struct(User{
Name: "",
Age: 0,
Address: "",
})
fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}Saída:
map[User.Address:Address deve terminar com o texto '市' User.Age:Age deve ser maior ou igual a 18 User.Name:Name deve conter o texto 'jack']Também é possível traduzir cada erro individualmente:
for _, fieldError := range err.(validator.ValidationErrors) {
fmt.Println(fieldError.Translate(trans))
}Saída:
Name deve conter o texto 'jack'
Age deve ser maior ou igual a 18
Address deve terminar com o texto '市'Pode-se ver que o valor de retorno é um map. A tradução básica das mensagens de erro já foi feita, mas ainda não é suficiente para uso em produção. Precisamos formatar melhor as mensagens de erro para facilitar a integração com clientes ou frontend.
type User struct {
Name string `validate:"contains=jack" label:"姓名"` // nome contém jack
Age int `validate:"gte=18" label:"年龄"` // maior ou igual a 18 anos
Address string `validate:"endswith=市" label:"地址"` // termina com 市
Sex string `validate:"required" label:"性别"`
}Primeiro, customiza-se a tag label, cujo valor é o nome em chinês do campo. Em seguida, registra-se um TagNameFunc através do validador. Sua função é obter o nome do campo ou substituir o nome original. O comentário no método Field() string no arquivo errors.go diz: "Nomes de campo com tag têm prioridade sobre o nome real do campo". Portanto, ao ocorrer erros, pode-se usar o nome em chinês customizado para substituir a palavra em inglês. TagNameFunc如下:
// Adicionamos uma tag customizada para dar nomes em chinês aos campos da struct, substituindo o nome original do campo
func CustomTagNameFunc(field reflect.StructField) string {
label := field.Tag.Get("label")
if len(label) == 0 {
return field.Name
}
return label
}Finalmente registra-se:
validate.RegisterTagNameFunc(CustomTagNameFunc)Executa-se novamente e a saída é:
姓名 deve conter o texto 'jack'
年龄 deve ser maior ou igual a 18
地址 deve terminar com o texto '市'Ainda não é suficiente para retornar como mensagem de erro ao frontend. Precisamos formatar as informações em JSON ou qualquer formato adequado para transmissão de mensagens. Você pode pensar em serializar o map diretamente para JSON, o que é uma solução, mas pode obter o seguinte resultado:
{
"User.地址": "地址必须以文本'市'结尾",
"User.姓名": "姓名必须包含文本'back'",
"User.年龄": "年龄必须大于或等于18",
"User.性别": "性别为必填字段"
}Processando a chave do map, obtém-se:
{
"地址": "地址必须以文本'市'结尾",
"姓名": "姓名必须包含文本'back'",
"年龄": "年龄必须大于或等于18",
"性别": "性别为必填字段"
}No entanto, não é recomendado retornar este tipo de informação ao frontend. Podemos formatar como uma string para retornar como mensagem:
姓名必须包含文本'back', 年龄必须大于或等于18, 地址必须以文本'市'结尾,性别为必填字段,Código completo:
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:"姓名"` // nome contém jack
Age int `validate:"gte=18" label:"年龄"` // maior ou igual a 18 anos
Address string `validate:"endswith=市" label:"地址"` // termina com 市
Sex string `validate:"required" label:"性别"`
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
// Adicionamos uma tag customizada para dar nomes em chinês aos campos da struct, substituindo o nome original do campo
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)
// o idioma aqui geralmente pode ser obtido do cabeçalho Accept-Language da requisição http
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // registra tradutor padrão
}
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()
}Por fim, se desejar que as mensagens de erro sejam mais humanizadas, é possível reescrever as mensagens de erro de tags específicas. Isso requer o uso do método RegisterTranslation, que também precisa de dois tipos de funções: RegisterTranslationsFunc responsável por registrar o modelo de tradução da tag correspondente, e TranslationFunc, responsável por processar o modelo para obter o conteúdo final da tradução. Aqui está um exemplo com required:
func requiredOverrideRegister(ut ut.Translator) error { // esta função registra o modelo de tradução
return ut.Add("required", "{} é um campo que deve ser preenchido", true) // {} é um placeholder, true representa sobrescrever modelo existente
}
func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // esta função é responsável por traduzir o conteúdo
t, _ := ut.T("required", fe.Field()) // pode haver múltiplos parâmetros, dependendo de quantos placeholders o modelo da tag tem
return t
}Finalmente registra-se:
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)Resultado:
姓名 deve conter o texto 'back', 年龄 deve ser maior ou igual a 18, 地址 deve terminar com o texto '市', 性别 é um campo que deve ser preenchido,Arquivo de Idioma
Na verdade, registrar via código é muito trabalhoso. universal-translator fornece a possibilidade de traduzir através de arquivos de configuração 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") // recomenda-se importar após o registro, para sobrescrever as tags existentes
if er != nil {
log.Fatal(er)
}
type Gopher struct {
Language string `validate:"required"`
}
err := validate.Struct(Gopher{
"",
})
fmt.Println(err.(validator.ValidationErrors).Translate(translator))
}Arquivo JSON:
[
{
"locale": "zh",
"key": "required",
"trans": "Este é um campo muito importante {0}, você deve preenchê-lo",
"override": true
}
]Saída:
map[Gopher.Language:Este é um campo muito importante Language,você deve preenchê-lo]TIP
universal-translator tem muitas armadilhas ao usar. Se desejar sobrescrever a Tag existente, type e rule podem não ser preenchidos, pois a configuração original também não os preencheu, é melhor manter consistência. O que for preenchido em type será adicionado ao map correspondente. Se for Cardinal ou outro type e rule estiver configurado com one ou similar, então é necessário fazer a configuração correspondente no locale para funcionar normalmente, caso contrário ocorrerá erro.
