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)
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
go get github.com/go-playground/validator/v10Import
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
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
| Tag | Descrizione |
|---|---|
eqcsfield | In una struct separata, verifica se il valore del campo corrente è uguale al campo specificato dal valore del parametro |
eqfield | Verifica se il valore del campo corrente è uguale al campo specificato dal valore del parametro |
fieldcontains | Verifica se il valore del campo corrente contiene il campo specificato dal valore del parametro |
fieldexcludes | Verifica se il valore del campo corrente non contiene il campo specificato dal valore del parametro |
gtcsfield | In una struct separata, verifica se il valore del campo corrente è maggiore del campo specificato dal valore del parametro |
gtecsfield | In una struct separata, verifica se il valore del campo corrente è maggiore o uguale al campo specificato dal valore del parametro |
gtefield | Verifica se il valore del campo corrente è maggiore o uguale al campo specificato dal valore del parametro |
gtfield | Verifica se il valore del campo corrente è maggiore del campo specificato dal valore del parametro |
ltcsfield | In una struct separata, verifica se il valore del campo corrente è minore del campo specificato dal valore del parametro |
ltecsfield | In una struct separata, verifica se il valore del campo corrente è minore o uguale al campo specificato dal valore del parametro |
ltefield | Verifica se il valore del campo corrente è minore o uguale al campo specificato dal valore del parametro |
ltfield | Verifica se il valore del campo corrente è minore del campo specificato dal valore del parametro |
necsfield | Verifica se il valore del campo corrente non è uguale al campo nella struct separata specificato dal valore del parametro |
nefield | Verifica se il valore del campo corrente non è uguale al campo specificato dal valore del parametro |
Rete
| Tag | Descrizione |
|---|---|
cidr | Classless Inter-Domain Routing CIDR |
cidrv4 | Classless Inter-Domain Routing CIDRv4 |
cidrv6 | Classless Inter-Domain Routing CIDRv6 |
datauri | Data Uniform Resource Identifier |
fqdn | Fully Qualified Domain Name (FQDN) |
hostname | Hostname RFC 952 |
hostname_port | Validazione campo per combinazioni <dns>:<port> usate per indirizzi 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 |
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 |
Stringhe
| Tag | Descrizione |
|---|---|
alpha | Verifica se il valore del campo è alfabetico valido |
alphanum | Verifica se il valore del campo è alfanumerico valido |
alphanumunicode | Verifica se il valore del campo è alfanumerico unicode valido |
alphaunicode | Verifica se il valore del campo è alfabetico unicode valido |
ascii | Verifica se il valore del campo è un carattere ASCII valido |
boolean | Verifica se il valore del campo è un booleano valido o convertibile in booleano |
contains | Verifica se il valore del campo contiene il testo specificato nel parametro |
containsany | Verifica se il valore del campo contiene qualsiasi carattere specificato nel parametro |
containsrune | Verifica se il valore del campo contiene il rune specificato nel parametro |
endsnotwith | Verifica se il valore del campo non termina con il testo specificato nel parametro |
endswith | Verifica se il valore del campo termina con il testo specificato nel parametro |
excludes | Verifica se il valore del campo non contiene il testo specificato nel parametro |
excludesall | Verifica se il valore del campo non contiene alcun carattere specificato nel parametro |
excludesrune | Verifica se il valore del campo non contiene il carattere specificato nel parametro |
lowercase | Verifica se il valore del campo è una stringa minuscola |
multibyte | Verifica se il valore del campo ha caratteri multibyte |
number | Verifica se il valore del campo è un numero valido |
numeric | Verifica se il valore del campo è un valore numerico valido |
printascii | Verifica se il valore del campo è un carattere ASCII stampabile valido |
startsnotwith | Verifica se il valore del campo non inizia con il testo specificato nel parametro |
startswith | Verifica se il valore del campo inizia con il testo specificato nel parametro |
uppercase | Verifica se il valore del campo è una stringa maiuscola |
Formattazione
| Tag | Descrizione |
|---|---|
base64 | Stringa Base64 |
base64url | Stringa Base64URL |
bic | Verifica se il valore del campo è un codice BIC valido (SWIFT code) secondo ISO 9362 |
bcp47_language_tag | Verifica se il valore del campo è un tag linguistico BCP47 |
btc_addr | Verifica se il valore del campo è un indirizzo BTC valido |
btc_addr_bech32 | Verifica se il valore del campo è un indirizzo bech32 BTC valido |
credit_card | Verifica se il valore del campo è un numero di carta di credito valido |
datetime | Verifica se il valore del campo è una stringa data/ora valida |
e164 | Verifica se il valore del campo è un numero di telefono formato e.164 valido |
email | Verifica se il valore del campo è un indirizzo email valido |
eth_addr | Verifica se il valore del campo è un indirizzo Ethereum valido |
hexadecimal | Verifica se il valore del campo è un esadecimale valido |
hexcolor | Verifica se il valore del campo è un colore esadecimale valido |
hsl | Verifica se il valore del campo è un colore HSL valido |
hsla | Verifica se il valore del campo è un colore HSLA valido |
html | Verifica se il valore del campo è HTML valido |
html_encoded | Verifica se il valore del campo è HTML encoded valido |
isbn | Verifica se il valore del campo è un ISBN v10 o v13 valido (International Standard Book Number) |
isbn10 | Verifica se il valore del campo è un ISBN v10 valido |
isbn13 | Verifica se il valore del campo è un ISBN v13 valido |
iso3166_1_alpha2 | Verifica se il valore del campo è un codice paese iso3166-1 alpha-2 valido |
iso3166_1_alpha3 | Verifica se il valore del campo è un codice paese iso3166-1 alpha-3 valido |
iso3166_1_alpha_numeric | Verifica se il valore del campo è un codice paese iso3166-1 alfanumerico valido |
iso3166_2 | Verifica se il valore del campo è un codice regione paese valido (ISO 3166-2) |
iso4217 | Verifica se il valore del campo è un codice valuta valido (ISO 4217) |
json | Verifica se il valore del campo è una stringa json valida |
jwt | Verifica se il valore del campo è una stringa JWT valida |
latitude | Verifica se il valore del campo è una coordinata di latitudine valida |
longitude | Verifica se il valore del campo è una coordinata di longitudine valida |
postcode_iso3166_alpha2 | Verifica in base al valore del codice paese iso 3166 alpha 2 |
postcode_iso3166_alpha2_field | Verifica tramite campo che indica il codice paese iso 3166 alpha 2 |
rgb | Verifica se il valore del campo è un colore RGB valido |
rgba | Verifica se il valore del campo è un colore RGBA valido |
ssn | Verifica se il valore del campo è un SSN valido |
timezone | Verifica se il valore del campo è una stringa fuso orario valida |
uuid | Verifica se il valore del campo è un UUID valido di qualsiasi versione |
uuid3 | Verifica se il valore del campo è un UUID v3 valido |
uuid3_rfc4122 | Verifica se il valore del campo è un UUID v3 RFC4122 valido |
uuid4 | Verifica se il valore del campo è un UUID v4 valido |
uuid4_rfc4122 | Verifica se il valore del campo è un UUID v4 RFC4122 valido |
uuid5 | Verifica se il valore del campo è un UUID v5 valido |
uuid5_rfc4122 | Verifica se il valore del campo è un UUID v5 RFC4122 valido |
uuid_rfc4122 | Verifica se il valore del campo è un UUID RFC4122 valido di qualsiasi versione |
md4 | Verifica se il valore del campo è un MD4 valido |
md5 | Verifica se il valore del campo è un MD5 valido |
sha256 | Verifica se il valore del campo è un SHA256 valido |
sha384 | Verifica se il valore del campo è un SHA384 valido |
sha512 | Verifica se il valore del campo è un SHA512 valido |
ripemd128 | Verifica se il valore del campo è un RIPEMD128 valido |
ripemd160 | Verifica se il valore del campo è un RIPEMD160 valido |
tiger128 | Verifica se il valore del campo è un TIGER128 valido |
tiger160 | Verifica se il valore del campo è un TIGER160 valido |
tiger192 | Verifica se il valore del campo è un TIGER192 valido |
semver | Verifica se il valore del campo è una versione semver valida secondo Semantic Versioning 2.0.0 |
ulid | Verifica se il valore del campo è un ULID valido |
Confronto
| Tag | Descrizione |
|---|---|
eq | Uguale a |
gt | Maggiore di |
gte | Maggiore o uguale |
lt | Minore di |
lte | Minore o uguale |
ne | Diverso da |
Altro
| Tag | Descrizione |
|---|---|
dir | Directory file |
file | Percorso file |
isdefault | Verifica se il valore del campo è il valore statico predefinito |
len | Lunghezza campo |
max | Valore massimo |
min | Valore minimo |
oneof | Verifica se è uno dei valori elencati |
omitempty | Se il campo non è impostato, ignora il campo |
required | Valore obbligatorio |
required_if | Il campo deve essere presente e non vuoto solo quando tutti gli altri campi specificati sono uguali al valore specificato |
required_unless | Il campo deve essere presente e non vuoto a meno che tutti gli altri campi specificati siano uguali al valore specificato |
required_with | Il campo deve essere presente e non vuoto quando uno qualsiasi dei campi specificati è presente |
required_with_all | Il campo deve essere presente e non vuoto quando tutti i campi specificati sono presenti |
required_without | Il campo deve essere presente e non vuoto quando uno qualsiasi dei campi specificati non è presente |
required_without_all | Il campo deve essere presente e non vuoto quando tutti i campi specificati non sono presenti |
excluded_if | Il campo può non essere presente o vuoto solo quando tutti gli altri campi specificati sono uguali al valore specificato |
excluded_unless | Il campo può non essere presente o vuoto a meno che tutti gli altri campi specificati siano uguali al valore specificato |
excluded_with | Il campo può non essere presente o vuoto quando uno qualsiasi dei campi specificati è presente |
excluded_with_all | Il campo può non essere presente o vuoto quando tutti i campi specificati sono presenti |
excluded_without | Il campo può non essere presente o vuoto quando uno qualsiasi dei campi specificati non è presente |
excluded_without_all | Il campo può non essere presente o vuoto quando tutti i campi specificati non sono presenti |
unique | Verifica se ogni valore arr, map, slice è unico |
Alias
| Tag | Descrizione |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
Operatori
| Tag | Descrizione | Hex |
|---|---|---|
, | Operatore AND, usa più tag di validazione, tutte le condizioni devono essere soddisfatte, nessun spazio tra le virgole | 0x2c |
| | Operatore OR, usa più tag di validazione, ma ne deve essere soddisfatto solo uno | 0x7c |
- | Salta la validazione per questo campo | 0x2d |
= | Simbolo di corrispondenza parametro | 0x3d |
TIP
Quando si vuole far corrispondere un operatore durante la validazione di un campo, è necessario sostituirlo con la sua rappresentazione esadecimale utf8, ad esempio
filed string `validate:"contains=0x2c"`Utilizzo
Di seguito verranno mostrati alcuni usi base di Validator con esempi di codice.
Singleton
var validate *validator.ValidateQuando 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.
validate = validator.New()Validazione Struct
func (v *Validate) Struct(s interface{}) errorIl 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
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' tagValidazione Map
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}Validazione chiave-valore tramite un tag map.
Esempio
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.
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' tagValidazione struct per ogni utente nella 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 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' tagValidazione Variabile
Semplice e intuitiva, non richiede molte spiegazioni.
Esempio 1
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' tagEsempio 2
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' tagTIP
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.
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:
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:
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
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' tagFunzioni 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:
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:
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tagTIP
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:
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
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:
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 '' tagMultilingua
Componente traduttore
go get github.com/go-playground/universal-translatorComponente regione
go get github.com/go-playground/localesLa 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:
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
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.
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:
// 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
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:
{
"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:
{
"地址": "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
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:
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
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)
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
[
{
"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.
