Skip to content

Validator Libreria di Validazione

Sito ufficiale: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)

Documentazione: validator/README.md at master · go-playground/validator (github.com)

Esempi ufficiali: 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)

Introduzione

go-playground/validator implementa un validatore di valori basato su tag di struct, con le seguenti caratteristiche uniche:

  • Può usare tag di validazione e validatori personalizzati per validazioni cross-field e cross-struct
  • Slice, array, map, o qualsiasi campo multidimensionale possono essere validati
  • Può validare in profondità chiavi e valori delle map
  • Determina come gestire in base al tipo di base prima della validazione
  • Può gestire tipi di campo personalizzati
  • Supporta tag alias, che permettono di mappare più validazioni su un singolo tag per definire più facilmente le validazioni per le struct
  • Può estrarre nomi di campo personalizzati, ad esempio può estrarre nomi JSON durante la validazione per mostrarli nei messaggi di errore
  • Messaggi di errore multilingua personalizzabili
  • Componente di validazione predefinito per il framework gin

Installazione

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

Import

go
import "github.com/go-playground/validator/v10"

Tag

Il validatore ha moltissimi tag di validazione di base, tutte le funzioni di validazione corrispondenti ai tag possono essere trovate nel file baked_in.go, il tag struct del validatore è validate,

ad esempio

go
type User {
  age int `validate:"gte=18"` // indica maggiore o uguale a 18 anni
}

Si può anche modificare il tag predefinito tramite il metodo setTagName.

Campi

TagDescrizione
eqcsfieldIn una struct separata, verifica se il valore del campo corrente è uguale al campo specificato dal valore del parametro
eqfieldVerifica se il valore del campo corrente è uguale al campo specificato dal valore del parametro
fieldcontainsVerifica se il valore del campo corrente contiene il campo specificato dal valore del parametro
fieldexcludesVerifica se il valore del campo corrente non contiene il campo specificato dal valore del parametro
gtcsfieldIn una struct separata, verifica se il valore del campo corrente è maggiore del campo specificato dal valore del parametro
gtecsfieldIn una struct separata, verifica se il valore del campo corrente è maggiore o uguale al campo specificato dal valore del parametro
gtefieldVerifica se il valore del campo corrente è maggiore o uguale al campo specificato dal valore del parametro
gtfieldVerifica se il valore del campo corrente è maggiore del campo specificato dal valore del parametro
ltcsfieldIn una struct separata, verifica se il valore del campo corrente è minore del campo specificato dal valore del parametro
ltecsfieldIn una struct separata, verifica se il valore del campo corrente è minore o uguale al campo specificato dal valore del parametro
ltefieldVerifica se il valore del campo corrente è minore o uguale al campo specificato dal valore del parametro
ltfieldVerifica se il valore del campo corrente è minore del campo specificato dal valore del parametro
necsfieldVerifica se il valore del campo corrente non è uguale al campo nella struct separata specificato dal valore del parametro
nefieldVerifica se il valore del campo corrente non è uguale al campo specificato dal valore del parametro

Rete

TagDescrizione
cidrClassless Inter-Domain Routing CIDR
cidrv4Classless Inter-Domain Routing CIDRv4
cidrv6Classless Inter-Domain Routing CIDRv6
datauriData Uniform Resource Identifier
fqdnFully Qualified Domain Name (FQDN)
hostnameHostname RFC 952
hostname_portValidazione campo per combinazioni <dns>:<port> usate per indirizzi 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
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

Stringhe

TagDescrizione
alphaVerifica se il valore del campo è alfabetico valido
alphanumVerifica se il valore del campo è alfanumerico valido
alphanumunicodeVerifica se il valore del campo è alfanumerico unicode valido
alphaunicodeVerifica se il valore del campo è alfabetico unicode valido
asciiVerifica se il valore del campo è un carattere ASCII valido
booleanVerifica se il valore del campo è un booleano valido o convertibile in booleano
containsVerifica se il valore del campo contiene il testo specificato nel parametro
containsanyVerifica se il valore del campo contiene qualsiasi carattere specificato nel parametro
containsruneVerifica se il valore del campo contiene il rune specificato nel parametro
endsnotwithVerifica se il valore del campo non termina con il testo specificato nel parametro
endswithVerifica se il valore del campo termina con il testo specificato nel parametro
excludesVerifica se il valore del campo non contiene il testo specificato nel parametro
excludesallVerifica se il valore del campo non contiene alcun carattere specificato nel parametro
excludesruneVerifica se il valore del campo non contiene il carattere specificato nel parametro
lowercaseVerifica se il valore del campo è una stringa minuscola
multibyteVerifica se il valore del campo ha caratteri multibyte
numberVerifica se il valore del campo è un numero valido
numericVerifica se il valore del campo è un valore numerico valido
printasciiVerifica se il valore del campo è un carattere ASCII stampabile valido
startsnotwithVerifica se il valore del campo non inizia con il testo specificato nel parametro
startswithVerifica se il valore del campo inizia con il testo specificato nel parametro
uppercaseVerifica se il valore del campo è una stringa maiuscola

Formattazione

TagDescrizione
base64Stringa Base64
base64urlStringa Base64URL
bicVerifica se il valore del campo è un codice BIC valido (SWIFT code) secondo ISO 9362
bcp47_language_tagVerifica se il valore del campo è un tag linguistico BCP47
btc_addrVerifica se il valore del campo è un indirizzo BTC valido
btc_addr_bech32Verifica se il valore del campo è un indirizzo bech32 BTC valido
credit_cardVerifica se il valore del campo è un numero di carta di credito valido
datetimeVerifica se il valore del campo è una stringa data/ora valida
e164Verifica se il valore del campo è un numero di telefono formato e.164 valido
emailVerifica se il valore del campo è un indirizzo email valido
eth_addrVerifica se il valore del campo è un indirizzo Ethereum valido
hexadecimalVerifica se il valore del campo è un esadecimale valido
hexcolorVerifica se il valore del campo è un colore esadecimale valido
hslVerifica se il valore del campo è un colore HSL valido
hslaVerifica se il valore del campo è un colore HSLA valido
htmlVerifica se il valore del campo è HTML valido
html_encodedVerifica se il valore del campo è HTML encoded valido
isbnVerifica se il valore del campo è un ISBN v10 o v13 valido (International Standard Book Number)
isbn10Verifica se il valore del campo è un ISBN v10 valido
isbn13Verifica se il valore del campo è un ISBN v13 valido
iso3166_1_alpha2Verifica se il valore del campo è un codice paese iso3166-1 alpha-2 valido
iso3166_1_alpha3Verifica se il valore del campo è un codice paese iso3166-1 alpha-3 valido
iso3166_1_alpha_numericVerifica se il valore del campo è un codice paese iso3166-1 alfanumerico valido
iso3166_2Verifica se il valore del campo è un codice regione paese valido (ISO 3166-2)
iso4217Verifica se il valore del campo è un codice valuta valido (ISO 4217)
jsonVerifica se il valore del campo è una stringa json valida
jwtVerifica se il valore del campo è una stringa JWT valida
latitudeVerifica se il valore del campo è una coordinata di latitudine valida
longitudeVerifica se il valore del campo è una coordinata di longitudine valida
postcode_iso3166_alpha2Verifica in base al valore del codice paese iso 3166 alpha 2
postcode_iso3166_alpha2_fieldVerifica tramite campo che indica il codice paese iso 3166 alpha 2
rgbVerifica se il valore del campo è un colore RGB valido
rgbaVerifica se il valore del campo è un colore RGBA valido
ssnVerifica se il valore del campo è un SSN valido
timezoneVerifica se il valore del campo è una stringa fuso orario valida
uuidVerifica se il valore del campo è un UUID valido di qualsiasi versione
uuid3Verifica se il valore del campo è un UUID v3 valido
uuid3_rfc4122Verifica se il valore del campo è un UUID v3 RFC4122 valido
uuid4Verifica se il valore del campo è un UUID v4 valido
uuid4_rfc4122Verifica se il valore del campo è un UUID v4 RFC4122 valido
uuid5Verifica se il valore del campo è un UUID v5 valido
uuid5_rfc4122Verifica se il valore del campo è un UUID v5 RFC4122 valido
uuid_rfc4122Verifica se il valore del campo è un UUID RFC4122 valido di qualsiasi versione
md4Verifica se il valore del campo è un MD4 valido
md5Verifica se il valore del campo è un MD5 valido
sha256Verifica se il valore del campo è un SHA256 valido
sha384Verifica se il valore del campo è un SHA384 valido
sha512Verifica se il valore del campo è un SHA512 valido
ripemd128Verifica se il valore del campo è un RIPEMD128 valido
ripemd160Verifica se il valore del campo è un RIPEMD160 valido
tiger128Verifica se il valore del campo è un TIGER128 valido
tiger160Verifica se il valore del campo è un TIGER160 valido
tiger192Verifica se il valore del campo è un TIGER192 valido
semverVerifica se il valore del campo è una versione semver valida secondo Semantic Versioning 2.0.0
ulidVerifica se il valore del campo è un ULID valido

Confronto

TagDescrizione
eqUguale a
gtMaggiore di
gteMaggiore o uguale
ltMinore di
lteMinore o uguale
neDiverso da

Altro

TagDescrizione
dirDirectory file
filePercorso file
isdefaultVerifica se il valore del campo è il valore statico predefinito
lenLunghezza campo
maxValore massimo
minValore minimo
oneofVerifica se è uno dei valori elencati
omitemptySe il campo non è impostato, ignora il campo
requiredValore obbligatorio
required_ifIl campo deve essere presente e non vuoto solo quando tutti gli altri campi specificati sono uguali al valore specificato
required_unlessIl campo deve essere presente e non vuoto a meno che tutti gli altri campi specificati siano uguali al valore specificato
required_withIl campo deve essere presente e non vuoto quando uno qualsiasi dei campi specificati è presente
required_with_allIl campo deve essere presente e non vuoto quando tutti i campi specificati sono presenti
required_withoutIl campo deve essere presente e non vuoto quando uno qualsiasi dei campi specificati non è presente
required_without_allIl campo deve essere presente e non vuoto quando tutti i campi specificati non sono presenti
excluded_ifIl campo può non essere presente o vuoto solo quando tutti gli altri campi specificati sono uguali al valore specificato
excluded_unlessIl campo può non essere presente o vuoto a meno che tutti gli altri campi specificati siano uguali al valore specificato
excluded_withIl campo può non essere presente o vuoto quando uno qualsiasi dei campi specificati è presente
excluded_with_allIl campo può non essere presente o vuoto quando tutti i campi specificati sono presenti
excluded_withoutIl campo può non essere presente o vuoto quando uno qualsiasi dei campi specificati non è presente
excluded_without_allIl campo può non essere presente o vuoto quando tutti i campi specificati non sono presenti
uniqueVerifica se ogni valore arr, map, slice è unico

Alias

TagDescrizione
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

Operatori

TagDescrizioneHex
,Operatore AND, usa più tag di validazione, tutte le condizioni devono essere soddisfatte, nessun spazio tra le virgole0x2c
|Operatore OR, usa più tag di validazione, ma ne deve essere soddisfatto solo uno0x7c
-Salta la validazione per questo campo0x2d
=Simbolo di corrispondenza parametro0x3d

TIP

Quando si vuole far corrispondere un operatore durante la validazione di un campo, è necessario sostituirlo con la sua rappresentazione esadecimale utf8, ad esempio

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

Utilizzo

Di seguito verranno mostrati alcuni usi base di Validator con esempi di codice.

Singleton

go
var validate *validator.Validate

Quando si usa, gli autori raccomandano che durante l'intero ciclo di vita del programma, ci sia solo un'istanza del validatore, questo favorirà la cache di alcuni dati.

Creazione Validatore

Quando si usa Validator da solo senza integrazione con altri framework, è necessario creare manualmente il validatore.

go
validate = validator.New()

Validazione Struct

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

Il metodo Struct viene usato per validare tutti i campi pubblici di una struct, di default esegue automaticamente la validazione nested delle struct, quando si passa un valore non valido o il valore è nil, restituirà InvalidValidationError, se la validazione fallisce restituisce ValidationErrors.

Esempio

go
package validate

import (
  "fmt"
  "github.com/go-playground/validator/v10"
  "testing"
)

type User struct {
  Name    string `validate:"contains=jack"` // nome contiene jack
  Age     int    `validate:"gte=18"`        // maggiore o uguale a 18 anni
  Address string `validate:"endswith=市"`    // termina con 市
}

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

Output

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

Validazione Map

go
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}

Validazione chiave-valore tramite un tag map.

Esempio

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

Output

map[age:Key: '' Error:Field validation for '' failed on the 'gte' tag name:Key: '' Error:Field validation for '' failed on the 'contains' tag]

Validazione Slice

Validazione slice di stringhe, il tag prima di dive valida la slice, il tag dopo dive valida i valori nella slice, lo stesso principio vale per slice nested, si usa dive per ogni dimensione.

go
func TestSlice1(t *testing.T) {
  list := []string{"jack", "mike", "lisa", "golang"}
  err := validator.New().Var(list, "max=5,dive,contains=a,min=5") // lunghezza massima slice 5, elementi devono contenere carattere a, lunghezza minima 5
  fmt.Println(err)
}

Output

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

Validazione struct per ogni utente nella 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 validazione profonda, quando l'elemento è una struct, esegue automaticamente la validazione della struct
   fmt.Println(err)
}

Output

Key: '[0].Age' Error:Field validation for 'Age' failed on the 'gte' tag

Validazione Variabile

Semplice e intuitiva, non richiede molte spiegazioni.

Esempio 1

go
func TestVar(t *testing.T) {
   name := "jack"
   err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") // lunghezza massima 5, minima 1, contiene lettera a, termina con lettera l
   fmt.Println(err)
}

Output

Key: '' Error:Field validation for '' failed on the 'endswith' tag

Esempio 2

go
func TestVar1(t *testing.T) {
   age := 18
   err := validator.New().Var(age, "gte=19")
   fmt.Println(err)
}

Output

Key: '' Error:Field validation for '' failed on the 'gte' tag

TIP

Il metodo Var può validare tipi che includono struct, variabili, slice, map, da usare ragionevolmente in combinazione con il tag dive.

Validazione Campo

I parametri della validazione campo non sono più tipi di base, ma nomi di campo di struct, possono essere campi propri o campi di struct nested.

go
type Password struct {
   FirstPassword  string `validate:"eqfield=SecondPassword"` // verifica se le due password inserite sono uguali
   SecondPassword string
}

type RegisterUser struct {
   Username string `validate:"necsfield=Password.FirstPassword"` // per sicurezza durante la registrazione, proibisce che password e username siano uguali
   Password Password
}

func TestCrossStructFieldValidate(t *testing.T) {
   validate = validator.New()
   // Fallimento
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gopher",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gophers",
      },
   }))
   // Successo
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gophers",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gopher",
      },
   }))
}

Output

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

Quando si usa la validazione campo, se il campo o la struct passata come parametro al Tag non esiste, verrà considerata come validazione fallita, ad esempio:

go
type Password struct {
   FirstPassword  string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
   SecondPassword string
}

Per errori di battitura come questo, è difficile da rilevare, e durante la validazione si mostrerà solo come validazione non passata, bisogna fare molta attenzione.

Avanzato

Di seguito verranno spiegati alcuni trucchi di uso avanzato e più operazioni personalizzate.

Alias Personalizzati

A volte, per un campo ci sono moltissimi tag di validazione, quando si vuole riutilizzare su un altro campo, si potrebbe copiare e incollare, ma questa non è la soluzione migliore, il metodo migliore è registrare un alias per migliorare la riusabilità, guarda l'esempio seguente:

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

Output

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

Funzioni di Validazione Personalizzate

Anche se i tag di validazione inclusi nel componente sono sufficienti per la maggior parte dei casi, a volte per alcune esigenze speciali è necessario definire la propria logica, Validator ci fornisce le API per personalizzare le funzioni di validazione. Guarda prima l'esempio seguente:

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"
}

Crea una funzione che verifica se il valore del campo è uguale a "666", e il suo Tag corrispondente è is666, output:

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

TIP

Se il Tag registrato esiste già, verrà sovrascritto da quello nuovo, cioè si può "riscrivere" la logica di validazione predefinita del Tag.

Funzioni di Validazione Tipo Personalizzate

Le funzioni di validazione tipo sono specifiche per un certo tipo, solitamente usate per tipi non di base, e si può anche sovrascrivere la validazione predefinita dei tipi di base, guarda l'esempio seguente:

go
type Address struct {
  name string
}

func TestCustomTypeValidate(t *testing.T) {
  validate = validator.New()
  validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // registra funzione di validazione tipo e il tipo corrispondente
  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 {
    // gestione errore
    if address.name == "" {
      return address.name
    }

    return value // restituisce il campo che rappresenta la validazione corretta
  }
  return nil
}

Output

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

TIP

Registrare più tipi in una funzione funziona allo stesso modo.

Funzioni di Validazione Struct Personalizzate

La differenza delle funzioni di validazione struct è che i parametri delle altre funzioni sono campi, mentre questa funzione ha come parametro la struct, guarda l'esempio seguente:

go
type People struct {
   FirstName string
   LastName  string
}

func TestCustomStructLevel(t *testing.T) {
   validate = validator.New()
   validate.RegisterStructValidation(PeopleValidate, People{}) // come la registrazione tipo, si possono passare più tipi di 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", "", "")
   }
}

Output

Key: 'People.FirstName' Error:Field validation for 'FirstName' failed on the '' tag
Key: 'People.LastName' Error:Field validation for 'LastName' failed on the '' tag

Multilingua

Componente traduttore

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

Componente regione

go get github.com/go-playground/locales

La lingua predefinita del validatore è l'inglese, ma quando si sviluppano progetti, potrebbe essere necessario usare più di una lingua, in questo caso serve il componente di internazionalizzazione multilingua, guarda l'esempio seguente:

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 contiene jack
   Age     int    `validate:"gte=18"`        // maggiore o uguale a 18 anni
   Address string `validate:"endswith=市"`    // termina con 市
}

var (
   uni      *ut.UniversalTranslator
   validate *validator.Validate
)

func TestTranslate(t *testing.T) {
   zh := zh.New()
   // il primo è di backup, i successivi sono le lingue supportate, possono essercene più di una
   uni = ut.New(zh, zh)
   // la lingua qui di solito si ottiene dall'header Accept-Language della richiesta http
   trans, found := uni.GetTranslator(zh.Locale())
   validate = validator.New()
   if found {
      zh_trans.RegisterDefaultTranslations(validate, trans) // registra traduttore predefinito
   }
   err := validate.Struct(User{
      Name:    "",
      Age:     0,
      Address: "",
   })
   fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}

Output

map[User.Address:Address deve terminare con testo '市' User.Age:Age deve essere maggiore o uguale a 18 User.Name:Name deve contenere testo 'jack']

Si può anche tradurre ogni errore singolarmente

go
for _, fieldError := range err.(validator.ValidationErrors) {
   fmt.Println(fieldError.Translate(trans))
}

Output

Name deve contenere testo 'jack'
Age deve essere maggiore o uguale a 18
Address deve terminare con testo '市'

Si può vedere che il valore di ritorno è una map, la traduzione base dei messaggi di errore è stata fatta, ma non è ancora sufficiente per l'uso, dobbiamo continuare ad abbellire i messaggi di errore per interfacciarli meglio con clienti o frontend.

go
type User struct {

   Name    string `validate:"contains=jack" label:"姓名"` // nome contiene jack
   Age     int    `validate:"gte=18" label:"年龄"`        // maggiore o uguale a 18 anni
   Address string `validate:"endswith=市" label:"地址"`    // termina con 市
   Sex     string `validate:"required" label:"性别"`
}

Prima si personalizza il tag label, il suo valore è il nome cinese del campo, poi si registra un TagNameFunc tramite il validatore, il suo scopo è ottenere il nome del campo o sostituire il nome originale. Il commento sul metodo Field() string nel file errors.go dice: "I nomi dei campi con tag label hanno priorità sui nomi effettivi dei campi", quindi quando si verificano errori, si può usare il nome cinese personalizzato per sostituire le parole inglesi. TagNameFunc come segue:

go
// Aggiungiamo un tag personalizzato, usato per dare un nome cinese ai campi della struct, sostituirà il nome originale del campo
func CustomTagNameFunc(field reflect.StructField) string {
   label := field.Tag.Get("label")
   if len(label) == 0 {
      return field.Name
   }
   return label
}

Infine si registra

go
validate.RegisterTagNameFunc(CustomTagNameFunc)

Esecuzione output

Nome deve contenere testo 'jack'
Età deve essere maggiore o uguale a 18
Indirizzo deve terminare con testo '市'

Ma questo non basta ancora, non è sufficiente come informazione di errore da restituire al frontend, dobbiamo formattare le informazioni in json o qualsiasi formato adatto alla trasmissione di messaggi, potresti pensare di serializzare direttamente la map in json, questa è una soluzione, ma potresti ottenere il seguente risultato:

json
{
  "User.地址": "Indirizzo deve terminare con testo '市'",
  "User.姓名": "Nome deve contenere testo 'back'",
  "User.年龄": "Età deve essere maggiore o uguale a 18",
  "User.性别": "Genere è un campo obbligatorio"
}

Elaborando la chiave della map si ottiene il seguente risultato:

json
{
  "地址": "Indirizzo deve terminare con testo '市'",
  "姓名": "Nome deve contenere testo 'back'",
  "年龄": "Età deve essere maggiore o uguale a 18",
  "性别": "Genere è un campo obbligatorio"
}

Ma non si consiglia di restituire questo tipo di informazioni al frontend, possiamo elaborarle in una stringa da restituire come messaggio

Nome deve contenere testo 'back', Età deve essere maggiore o uguale a 18, Indirizzo deve terminare con testo '市', Genere è un campo obbligatorio,

Codice 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 contiene jack
   Age     int    `validate:"gte=18" label:"年龄"`        // maggiore o uguale a 18 anni
   Address string `validate:"endswith=市" label:"地址"`    // termina con 市
   Sex     string `validate:"required" label:"性别"`
}

var (
   uni      *ut.UniversalTranslator
   validate *validator.Validate
)

// Aggiungiamo un tag personalizzato, usato per dare un nome cinese ai campi della struct, sostituirà il nome originale del 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)
   // la lingua qui di solito si ottiene dall'header Accept-Language della richiesta http
   trans, found := uni.GetTranslator(zh.Locale())
   validate = validator.New()
   if found {
      zh_trans.RegisterDefaultTranslations(validate, trans) // registra traduttore predefinito
   }
   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()
}

Infine, se si desidera che i messaggi di errore siano più umani e meno freddi, si può riscrivere il messaggio di errore per tag specifici, questo richiede l'uso del metodo RegisterTranslation, e servono anche due tipi di funzioni, RegisterTranslationsFunc che registra il template di traduzione per il Tag corrispondente, e TranslationFunc che elabora il template per ottenere la traduzione finale. Ecco un esempio con required:

go
func requiredOverrideRegister(ut ut.Translator) error { // questa funzione registra il template di traduzione
  return ut.Add("required", "{} è un campo obbligatorio da compilare", true) // {} è un placeholder, true significa sovrascrivere template esistenti
}

func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // questa funzione gestisce la traduzione
  t, _ := ut.T("required", fe.Field()) // i parametri possono essere multipli, dipende da quanti placeholder ha il template del Tag registrato
  return t
}

Infine si registra

go
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)

Risultato

Nome deve contenere testo 'back', Età deve essere maggiore o uguale a 18, Indirizzo deve terminare con testo '市', Genere è un campo obbligatorio da compilare,

File Lingua

In realtà, registrare codice per codice è molto laborioso, universal-translator fornisce la possibilità di usare file di configurazione JSON per le traduzioni: 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") // si consiglia di importare dopo la registrazione, così si sovrascrivono i Tag esistenti
   if er != nil {
      log.Fatal(er)
   }
   type Gopher struct {
      Language string `validate:"required"`
   }

   err := validate.Struct(Gopher{
      "",
   })
   fmt.Println(err.(validator.ValidationErrors).Translate(translator))
}

File JSON

json
[
  {
    "locale": "zh",
    "key": "required",
    "trans": "Questo è un campo molto importante {0}, devi compilarlo",
    "override": true
  }
]

Output

map[Gopher.Language:Questo è un campo molto importante Language, devi compilarlo]

TIP

universal-translator ha molte insidie durante l'uso, se si vuole sovrascrivere un Tag esistente, type e rule possono essere lasciati vuoti, perché non sono stati compilati nella configurazione originale, è meglio mantenere la coerenza. Qualsiasi type venga compilato, la configurazione verrà aggiunta alla map corrispondente, se è Cardinal o altro type e rule è configurato con one o simili, allora è necessario fare la configurazione corrispondente a livello locale per usarlo correttamente, altrimenti si verificherà un errore.

Golang by www.golangdev.cn edit