Skip to content

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)

Benchmark: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (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

powershell
go get github.com/go-playground/validator/v10

Importação

go
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:

go
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

TagDescrição
eqcsfieldEm uma struct separada, valida se o valor do campo atual é igual ao campo especificado pelo valor do parâmetro
eqfieldValida se o valor do campo atual é igual ao campo especificado pelo valor do parâmetro
fieldcontainsValida se o valor do campo atual contém o campo especificado pelo valor do parâmetro
fieldexcludesValida se o valor do campo atual não contém o campo especificado pelo valor do parâmetro
gtcsfieldEm uma struct separada, valida se o valor do campo atual é maior que o campo especificado pelo valor do parâmetro
gtecsfieldEm uma struct separada, valida se o valor do campo atual é maior ou igual ao campo especificado pelo valor do parâmetro
gtefieldValida se o valor do campo atual é maior ou igual ao campo especificado pelo valor do parâmetro
gtfieldValida se o valor do campo atual é maior que o campo especificado pelo valor do parâmetro
ltcsfieldEm uma struct separada, valida se o valor do campo atual é menor que o campo especificado pelo valor do parâmetro
ltecsfieldEm uma struct separada, valida se o valor do campo atual é menor ou igual ao campo especificado pelo valor do parâmetro
ltefieldValida se o valor do campo atual é menor ou igual ao campo especificado pelo valor do parâmetro
ltfieldValida se o valor do campo atual é menor que o campo especificado pelo valor do parâmetro
necsfieldValida se o valor do campo atual não é igual ao campo em uma struct separada especificado pelo valor do parâmetro
nefieldValida se o valor do campo atual não é igual ao campo especificado pelo valor do parâmetro

Rede

TagDescrição
cidrClassless Inter-Domain Routing CIDR
cidrv4Classless Inter-Domain Routing CIDRv4
cidrv6Classless Inter-Domain Routing CIDRv6
datauriData Uniform Resource Locator
fqdnFully Qualified Domain Name (FQDN)
hostnameHostname RFC 952
hostname_portValidação de campo de combinação <dns>:<port> comumente usada para endereços de socket
hostname_rfc1123Hostname RFC 952
ipInternet Protocol Address IP
ip4_addrInternet Protocol Address IPv4
ip6_addrInternet Protocol Address IPv6
ip_addrInternet Protocol Address IP
ipv4Internet Protocol Address IPv4
ipv6Internet Protocol Address IPv6
macMedia Access Control Address, também conhecido como endereço LAN
tcp4_addrTransmission Control Protocol Address TCP4
tcp6_addrTransmission Control Protocol Address TCPv6
tcp_addrTransmission Control Protocol Address TCP
udp4_addrUser Datagram Protocol Address UDPv4
udp6_addrUser Datagram Protocol Address UDPv6
udp_addrUser Datagram Protocol Address UDP
unix_addrUnix Domain Socket Endpoint Address
uriUniform Resource Identifier
urlUniform Resource Locator
url_encodedUniform Resource Identifier Encoding
urn_rfc2141RFC 2141 Uniform Resource Name

Strings

TagDescrição
alphaValida se o valor do campo atual é uma letra válida
alphanumValida se o valor do campo atual é alfanumérico válido
alphanumunicodeValida se o valor do campo atual é um valor alfanumérico unicode válido
alphaunicodeValida se o valor do campo atual é um valor de letra unicode válido
asciiValida se o valor do campo é um caractere ASCII válido
booleanValida se o valor do campo atual é um valor booleano válido ou pode ser convertido com segurança para booleano
containsValida se o valor do campo contém o texto especificado no parâmetro
containsanyValida se o valor do campo contém qualquer caractere especificado no parâmetro
containsruneValida se o valor do campo contém o rune especificado no parâmetro
endsnotwithValida se o valor do campo não termina com o texto especificado no parâmetro
endswithValida se o valor do campo termina com o texto especificado no parâmetro
excludesValida se o valor do campo não contém o texto especificado no parâmetro
excludesallValida se o valor do campo não contém nenhum dos caracteres especificados no parâmetro
excludesruneValida se o valor do campo não contém o caractere especificado no parâmetro
lowercaseValida se o valor do campo atual é uma string em minúsculas
multibyteValida se o valor do campo possui caracteres multibyte
numberValida se o valor do campo atual é um número válido
numericValida se o valor do campo atual é um valor numérico válido
printasciiValida se o valor do campo é um caractere ASCII imprimível válido
startsnotwithValida se o valor do campo não começa com o texto especificado no parâmetro
startswithValida se o valor do campo começa com o texto especificado no parâmetro
uppercaseValida se o valor do campo atual é uma string em maiúsculas

Formatação

TagDescrição
base64String Base64
base64urlString Base64URL
bicValida se o valor do campo atual é um código BIC válido conforme definido no ISO 9362 (código SWIFT)
bcp47_language_tagValida se o valor do campo atual é uma tag de idioma da especificação BCP47
btc_addrValida se o valor do campo é um endereço BTC válido
btc_addr_bech32Valida se o valor do campo é um endereço bech32 BTC válido
credit_cardValida se o valor do campo atual é um número de cartão de crédito válido
datetimeValida se o valor do campo atual é uma string de data/hora válida
e164Valida se o valor do campo atual é um número de telefone no formato e.164 válido
emailValida se o valor do campo atual é um endereço de e-mail válido
eth_addrValida se o valor do campo é um endereço Ethereum válido
hexadecimalValida se o valor do campo atual é um hexadecimal válido
hexcolorValida se o valor do campo atual é uma cor hexadecimal válida
hslValida se o valor do campo atual é uma cor HSL válida
hslaValida se o valor do campo atual é uma cor HSLA válida
htmlValida se o valor do campo atual é um HTML válido
html_encodedValida se o valor do campo atual é uma codificação HTML válida
isbnValida se o valor do campo é um ISBN v10 ou v13 válido (International Standard Book Number)
isbn10Valida se o valor do campo é um ISBN v10 válido (International Standard Book Number)
isbn13Valida se o valor do campo é um ISBN v13 válido (International Standard Book Number)
iso3166_1_alpha2Valida se o valor do campo atual é um código de país iso3166-1 alpha-2 válido
iso3166_1_alpha3Valida se o valor do campo atual é um código de país iso3166-1 alpha-3 válido
iso3166_1_alpha_numericValida se o valor do campo atual é um código de país alfanumérico iso3166-1 válido
iso3166_2Valida se o valor do campo atual é um código de região de país válido (ISO 3166-2)
iso4217Valida se o valor do campo atual é um código de moeda válido (ISO 4217)
jsonValida se o valor do campo atual é uma string json válida
jwtValida se o valor do campo atual é uma string JWT válida
latitudeValida se o valor do campo é uma coordenada de latitude válida
longitudeValida se o valor do campo é uma coordenada de longitude válida
postcode_iso3166_alpha2Valida com base no valor do código de país iso 3166 alpha 2
postcode_iso3166_alpha2_fieldValida através de campo, onde o campo indica o valor do código de país iso 3166 alpha 2
rgbValida se o valor do campo atual é uma cor RGB válida
rgbaValida se o valor do campo atual é uma cor RGBA válida
ssnValida se o valor do campo é um SSN válido
timezoneValida se o valor do campo atual é uma string de fuso horário válida
uuidValida se o valor do campo é um UUID válido de qualquer versão
uuid3Valida se o valor do campo é um UUID v3 válido
uuid3_rfc4122Valida se o valor do campo é um UUID v3 RFC4122 válido
uuid4Valida se o valor do campo é um UUID v4 válido
uuid4_rfc4122Valida se o valor do campo é um UUID v4 RFC4122 válido
uuid5Valida se o valor do campo é um UUID v5 válido
uuid5_rfc4122Valida se o valor do campo é um UUID v5 RFC4122 válido
uuid_rfc4122Valida se o valor do campo é um UUID RFC4122 válido de qualquer versão
md4Valida se o valor do campo é um MD4 válido
md5Valida se o valor do campo é um MD5 válido
sha256Valida se o valor do campo é um SHA256 válido
sha384Valida se o valor do campo é um SHA384 válido
sha512Valida se o valor do campo é um SHA512 válido
ripemd128Valida se o valor do campo é um RIPEMD128 válido
ripemd160Valida se o valor do campo é um RIPEMD160 válido
tiger128Valida se o valor do campo é um TIGER128 válido
tiger160Valida se o valor do campo é um TIGER160 válido
tiger192Valida se o valor do campo é um TIGER192 válido
semverValida se o valor do campo atual é uma versão semver válida conforme definido no Semantic Versioning 2.0.0
ulidValida se o valor do campo é um ULID válido

Comparação

TagDescrição
eqIgual a
gtMaior que
gteMaior ou igual a
ltMenor que
lteMenor ou igual a
neDiferente de

Outros

TagDescrição
dirDiretório de arquivo
fileCaminho do arquivo
isdefaultValida se o valor do campo atual é o valor estático padrão
lenComprimento do campo
maxValor máximo
minValor mínimo
oneofSe é um dos valores listados
omitemptySe o campo não estiver definido, ignora o campo
requiredValor obrigatório
required_ifValida que o campo deve existir e não estar vazio apenas quando todos os outros campos especificados são iguais ao valor especificado
required_unlessValida que o campo deve existir e não estar vazio a menos que todos os outros campos especificados sejam iguais ao valor especificado
required_withValida que o campo deve existir e não estar vazio quando qualquer um dos campos especificados existe
required_with_allValida que o campo deve existir e não estar vazio quando todos os campos especificados existem
required_withoutValida que o campo deve existir e não estar vazio quando qualquer um dos campos especificados não existe
required_without_allValida que o campo deve existir e não estar vazio quando todos os campos especificados não existem
excluded_ifValida que o campo pode não existir ou estar vazio apenas quando todos os outros campos especificados são iguais ao valor especificado
excluded_unlessValida que o campo pode não existir ou estar vazio a menos que todos os outros campos especificados sejam iguais ao valor especificado
excluded_withValida que o campo pode não existir ou estar vazio quando qualquer um dos campos especificados existe
excluded_with_allValida que o campo pode não existir ou estar vazio quando todos os campos especificados existem
excluded_withoutValida que o campo pode não existir ou estar vazio quando qualquer um dos campos especificados não existe
excluded_without_allValida que o campo pode não existir ou estar vazio quando todos os campos especificados não existem
uniqueValida se cada valor de arr, map, slice é único

Alias

TagDescrição
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

Operadores

TagDescriçãoHex
,Operador E, usa múltiplas tags de validação, todas as condições devem ser satisfeitas, sem espaços entre vírgulas0x2c
|Operador OU, usa múltiplas tags de validação, mas apenas uma precisa ser satisfeita0x7c
-Pula a validação para este campo0x2d
=Símbolo de correspondência de parâmetro0x3d

TIP

Ao validar campos, para corresponder aos operadores, é necessário usar a representação hexadecimal utf8. Por exemplo:

go
filed string `validate:"contains=0x2c"`

Uso

Abaixo serão apresentados alguns usos básicos do Validator com exemplos de código.

Singleton

go
var validate *validator.Validate

Ao 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.

go
validate = validator.New()

Validação de Struct

go
func (v *Validate) Struct(s interface{}) error

O 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:

go
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' tag

Validação de Map

go
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:

go
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.

go
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' tag

Validação de struct para cada usuário no slice:

go
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' tag

Validação de Variável

Simples e fácil de entender, não requer muita explicação.

Exemplo 1:

go
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' tag

Exemplo 2:

go
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' tag

TIP

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.

go
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:

go
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:

go
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:

go
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

Funçã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:

go
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:

go
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tag

TIP

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:

go
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:

go
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:

go
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 '' tag

Multi-idiomas

Componente tradutor:

go get github.com/go-playground/universal-translator

Componente de região:

go get github.com/go-playground/locales

O 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:

go
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:

go
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.

go
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如下:

go
// 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:

go
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:

json
{
  "User.地址": "地址必须以文本'市'结尾",
  "User.姓名": "姓名必须包含文本'back'",
  "User.年龄": "年龄必须大于或等于18",
  "User.性别": "性别为必填字段"
}

Processando a chave do map, obtém-se:

json
{
  "地址": "地址必须以文本'市'结尾",
  "姓名": "姓名必须包含文本'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:

go
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:

go
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:

go
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)

go
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:

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.

Golang por www.golangdev.cn edit