Bibliothèque de validation Validator
Adresse officielle : go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)
Adresse de la documentation : validator/README.md at master · go-playground/validator (github.com)
Exemples officiels : validator/_examples at master · go-playground/validator (github.com)
Introduction
go-playground/validator implémente un validateur de valeurs basé sur des tags de structure, avec les caractéristiques uniques suivantes :
Validation inter-champs et inter-structures possible en utilisant des tags de validation et des validateurs personnalisés
Les slices, arrays, maps, ou tout champ multidimensionnel peuvent être validés
Possibilité de valider en profondeur les clés et valeurs des maps
Avant la validation, détermination du traitement à effectuer selon le type de base
Gestion des types de champs personnalisés
Support des tags alias, permettant de mapper plusieurs validations sur un seul tag pour faciliter la définition de validations sur les structures
Possibilité d'extraire des noms de champs personnalisés, par exemple extraire le nom JSON lors de la validation pour l'afficher dans les messages d'erreur
Messages d'erreur multilingues personnalisés
Composant de validation standard par défaut du framework
gin
Installation
go get github.com/go-playground/validator/v10Import
import "github.com/go-playground/validator/v10"Tags
Le validateur dispose de nombreux tags de validation de base, toutes les fonctions de validation correspondant aux tags se trouvent dans le fichier baked_in.go, le tag de structure du validateur est validate,
Par exemple
type User {
age int `validate:"gte=18"` // signifie supérieur ou égal à 18 ans
}Il est également possible de modifier le tag par défaut via la méthode setTagName.
Champs
| Tag | Description |
|---|---|
eqcsfield | Dans une structure séparée, valide si la valeur du champ actuel est égale au champ spécifié par la valeur du paramètre |
eqfield | Valide si la valeur du champ actuel est égale au champ spécifié par la valeur du paramètre |
fieldcontains | Valide si la valeur du champ actuel contient le champ spécifié par la valeur du paramètre |
fieldexcludes | Valide si la valeur du champ actuel ne contient pas le champ spécifié par la valeur du paramètre |
gtcsfield | Dans une structure séparée, valide si la valeur du champ actuel est supérieure au champ spécifié par la valeur du paramètre |
gtecsfield | Dans une structure séparée, valide si la valeur du champ actuel est supérieure ou égale au champ spécifié par la valeur du paramètre |
gtefield | Valide si la valeur du champ actuel est supérieure ou égale au champ spécifié par la valeur du paramètre |
gtfield | Valide si la valeur du champ actuel est supérieure au champ spécifié par la valeur du paramètre |
ltcsfield | Dans une structure séparée, valide si la valeur du champ actuel est inférieure au champ spécifié par la valeur du paramètre |
ltecsfield | Dans une structure séparée, valide si la valeur du champ actuel est inférieure ou égale au champ spécifié par la valeur du paramètre |
ltefield | Valide si la valeur du champ actuel est inférieure ou égale au champ spécifié par la valeur du paramètre |
ltfield | Valide si la valeur du champ actuel est inférieure au champ spécifié par la valeur du paramètre |
necsfield | Valide si la valeur du champ actuel n'est pas égale au champ dans la structure séparée spécifiée par la valeur du paramètre |
nefield | Valide si la valeur du champ actuel n'est pas égale au champ spécifié par la valeur du paramètre |
Réseau
| Tag | Description |
|---|---|
cidr | Routage Inter-Domaine Sans Classe CIDR |
cidrv4 | Routage Inter-Domaine Sans Classe CIDRv4 |
cidrv6 | Routage Inter-Domaine Sans Classe CIDRv6 |
datauri | URI de données |
fqdn | Nom de domaine pleinement qualifié (FQDN) |
hostname | Nom d'hôte RFC 952 |
hostname_port | Validation de champ généralement utilisée pour les adresses de socket, combinaison <dns>:<port> |
hostname_rfc1123 | Nom d'hôte RFC 952 |
ip | Adresse IP |
ip4_addr | Adresse IPv4 |
ip6_addr | Adresse IPv6 |
ip_addr | Adresse IP |
ipv4 | Adresse IPv4 |
ipv6 | Adresse IPv6 |
mac | Adresse MAC, aussi appelée adresse LAN |
tcp4_addr | Adresse TCP4 |
tcp6_addr | Adresse TCPv6 |
tcp_addr | Adresse TCP |
udp4_addr | Adresse UDPv4 |
udp6_addr | Adresse UDPv6 |
udp_addr | Adresse UDP |
unix_addr | Adresse de point de terminaison de socket Unix |
uri | URI |
url | URL |
url_encoded | Encodage URI |
urn_rfc2141 | URN RFC 2141 |
Chaînes de caractères
| Tag | Description |
|---|---|
alpha | Valide si la valeur du champ actuel est une lettre valide |
alphanum | Valide si la valeur du champ actuel est une valeur alphanumérique valide |
alphanumunicode | Valide si la valeur du champ actuel est une valeur alphanumérique unicode valide |
alphaunicode | Valide si la valeur du champ actuel est une lettre unicode valide |
ascii | Valide si la valeur du champ est un caractère ASCII valide |
boolean | Valide si la valeur du champ actuel est une valeur booléenne valide ou peut être convertie en toute sécurité en booléen |
contains | Valide si la valeur du champ contient le texte spécifié dans le paramètre |
containsany | Valide si la valeur du champ contient l'un des caractères spécifiés dans le paramètre |
containsrune | Valide si la valeur du champ contient le rune spécifié dans le paramètre |
endsnotwith | Valide si la valeur du champ ne se termine pas par le texte spécifié dans le paramètre |
endswith | Valide si la valeur du champ se termine par le texte spécifié dans le paramètre |
excludes | Valide si la valeur du champ ne contient pas le texte spécifié dans le paramètre |
excludesall | Valide si la valeur du champ ne contient aucun des caractères spécifiés dans le paramètre |
excludesrune | Valide si la valeur du champ ne contient pas le caractère spécifié dans le paramètre |
lowercase | Valide si la valeur du champ actuel est une chaîne en minuscules |
multibyte | Valide si la valeur du champ contient des caractères multi-octets |
number | Valide si la valeur du champ actuel est un nombre valide |
numeric | Valide si la valeur du champ actuel est une valeur numérique |
printascii | Valide si la valeur du champ est un caractère ASCII imprimable valide |
startsnotwith | Valide si la valeur du champ ne commence pas par le texte spécifié dans le paramètre |
startswith | Valide si la valeur du champ commence par le texte spécifié dans le paramètre |
uppercase | Valide si la valeur du champ actuel est une chaîne en majuscules |
Formatage
| Tag | Description |
|---|---|
base64 | Chaîne Base64 |
base64url | Chaîne Base64URL |
bic | Valide si la valeur du champ actuel est un code BIC valide (code SWIFT) défini dans ISO 9362 |
bcp47_language_tag | Valide si la valeur du champ actuel est une étiquette de langue conforme à BCP47 |
btc_addr | Valide si la valeur du champ est une adresse BTC valide |
btc_addr_bech32 | Valide si la valeur du champ est une adresse BTC bech32 valide |
credit_card | Valide si la valeur du champ actuel est un numéro de carte de crédit valide |
datetime | Valide si la valeur du champ actuel est une chaîne de date/heure valide |
e164 | Valide si la valeur du champ actuel est un numéro de téléphone au format e.164 valide |
email | Valide si la valeur du champ actuel est une adresse email valide |
eth_addr | Valide si la valeur du champ est une adresse Ethereum valide |
hexadecimal | Valide si la valeur du champ actuel est un hexadécimal valide |
hexcolor | Valide si la valeur du champ actuel est une couleur hexadécimale valide |
hsl | Valide si la valeur du champ actuel est une couleur HSL valide |
hsla | Valide si la valeur du champ actuel est une couleur HSLA valide |
html | Valide si la valeur du champ actuel est un HTML valide |
html_encoded | Valide si la valeur du champ actuel est un encodage HTML valide |
isbn | Valide si la valeur du champ est un ISBN (International Standard Book Number) v10 ou v13 valide |
isbn10 | Valide si la valeur du champ est un ISBN v10 (International Standard Book Number) valide |
isbn13 | Valide si la valeur du champ est un ISBN v13 (International Standard Book Number) valide |
iso3166_1_alpha2 | Valide si la valeur du champ actuel est un code pays iso3166-1 alpha-2 valide |
iso3166_1_alpha3 | Valide si la valeur du champ actuel est un code pays iso3166-1 alpha-3 valide |
iso3166_1_alpha_numeric | Valide si la valeur du champ actuel est un code pays alphanumérique iso3166-1 valide |
iso3166_2 | Valide si la valeur du champ actuel est un code de région de pays valide (ISO 3166-2) |
iso4217 | Valide si la valeur du champ actuel est un code de devise valide (ISO 4217) |
json | Valide si la valeur du champ actuel est une chaîne json valide |
jwt | Valide si la valeur du champ actuel est une chaîne JWT valide |
latitude | Valide si la valeur du champ est une coordonnée de latitude valide |
longitude | Valide si la valeur du champ est une coordonnée de latitude valide |
postcode_iso3166_alpha2 | Valide selon la valeur du code pays iso 3166 alpha 2 |
postcode_iso3166_alpha2_field | Valide via un champ qui représente la valeur du code pays dans iso 3166 alpha 2 |
rgb | Valide si la valeur du champ actuel est une couleur RGB valide |
rgba | Valide si la valeur du champ actuel est une couleur RGBA valide |
ssn | Valide si la valeur du champ est un SSN valide |
timezone | Valide si la valeur du champ actuel est une chaîne de fuseau horaire valide |
uuid | Valide si la valeur du champ est un UUID valide de n'importe quelle version |
uuid3 | Valide si la valeur du champ est un UUID v3 valide |
uuid3_rfc4122 | Valide si la valeur du champ est un UUID v3 RFC4122 valide |
uuid4 | Valide si la valeur du champ est un UUID v4 valide |
uuid4_rfc4122 | Valide si la valeur du champ est un UUID v4 RFC4122 valide |
uuid5 | Valide si la valeur du champ est un UUID v5 valide |
uuid5_rfc4122 | Valide si la valeur du champ est un UUID v5 RFC4122 valide |
uuid_rfc4122 | Valide si la valeur du champ est un UUID RFC4122 valide de n'importe quelle version |
md4 | Valide si la valeur du champ est un MD4 valide |
md5 | Valide si la valeur du champ est un MD5 valide |
sha256 | Valide si la valeur du champ est un SHA256 valide |
sha384 | Valide si la valeur du champ est un SHA384 valide |
sha512 | Valide si la valeur du champ est un SHA512 valide |
ripemd128 | Valide si la valeur du champ est un PIPEMD128 valide |
ripemd128 | Valide si la valeur du champ est un PIPEMD160 valide |
tiger128 | Valide si la valeur du champ est un TIGER128 valide |
tiger160 | Valide si la valeur du champ est un TIGER160 valide |
tiger192 | Valide si la valeur du champ est un TIGER192 valide |
semver | Valide si la valeur du champ actuel est une version semver valide définie dans la sémantique de version 2.0.0 |
ulid | Valide si la valeur du champ est un ULID valide |
Comparaison
| Tag | Description |
|---|---|
eq | Égal à |
gt | Supérieur à |
gte | Supérieur ou égal à |
lt | Inférieur à |
lte | Inférieur ou égal à |
ne | Différent de |
Autres
| Tag | Description |
|---|---|
dir | Répertoire de fichiers |
file | Chemin de fichier |
isdefault | Valide si la valeur du champ actuel est une valeur statique par défaut |
len | Longueur du champ |
max | Valeur maximale |
min | Valeur minimale |
oneof | Si la valeur est l'une des valeurs listées |
oimtempty | Si le champ n'est pas défini, ignorer le champ |
required | Valeur obligatoire |
required_if | Valide que le champ doit exister et ne pas être vide uniquement si tous les autres champs spécifiés sont égaux aux valeurs spécifiées |
required_unless | Valide que le champ doit exister et ne pas être vide sauf si tous les autres champs spécifiés sont égaux aux valeurs spécifiées |
required_with | Valide que le champ doit exister et ne pas être vide si l'un des champs spécifiés existe |
required_with_all | Valide que le champ doit exister et ne pas être vide si tous les champs spécifiés existent |
required_without | Valide que le champ doit exister et ne pas être vide si l'un des champs spécifiés n'existe pas |
required_without_all | Valide que le champ doit exister et ne pas être vide si tous les champs spécifiés n'existent pas |
excluded_if | Valide que le champ peut ne pas exister ou être vide uniquement si tous les autres champs spécifiés sont égaux aux valeurs spécifiées |
excluded_unless | Valide que le champ peut ne pas exister ou être vide sauf si tous les autres champs spécifiés sont égaux aux valeurs spécifiées |
excluded_with | Valide que le champ peut ne pas exister ou être vide si l'un des champs spécifiés existe |
excluded_with_all | Valide que le champ peut ne pas exister ou être vide si tous les champs spécifiés existent |
excluded_without | Valide que le champ peut ne pas exister ou être vide si l'un des champs spécifiés n'existe pas |
excluded_without_all | Valide que le champ peut ne pas exister ou être vide si tous les champs spécifiés n'existent pas |
unique | Valide si chaque valeur arr, map, slice est unique |
Alias
| Tag | Description |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
Opérateurs
| Tag | Description | Hex | | --- | -------------------------------------------------------------------- | -------------------------------------------------- | ------ | | , | Opérateur ET, utilise plusieurs tags de validation, toutes les conditions doivent être satisfaites, pas d'espace entre les virgules | 0x2c | | | | Opérateur OU, utilise plusieurs tags de validation, une seule condition doit être satisfaite | 0x7c | | - | Ce champ est ignoré lors de la validation | 0x2d | | = | Symbole de correspondance des paramètres | 0x3d |
TIP
Lors de la validation de champs, si vous souhaitez faire correspondre des opérateurs, vous devez utiliser la forme hexadécimale utf8 pour remplacer, par exemple
filed string `validate:"contains=0x2c"`Utilisation
Ci-dessous, nous présenterons quelques utilisations de base de Validator ainsi que quelques exemples de code.
Singleton
var validate *validator.ValidateLors de l'utilisation, il est recommandé de n'avoir qu'une seule instance de validateur pendant tout le cycle de vie du programme, cela favorise la mise en cache de certaines données.
Créer un validateur
Lors de l'utilisation autonome de Validator sans intégration avec d'autres frameworks, vous devez créer manuellement le validateur.
validate = validator.New()Validation de structure
func (v *Validate) Struct(s interface{}) errorLa méthode Struct est utilisée pour valider tous les champs publics d'une structure, elle effectue automatiquement la validation des structures imbriquées par défaut, lorsqu'une valeur illégale est passée ou lorsque la valeur passée est nil, elle retourne InvalidValidationError, si la validation échoue, elle retourne ValidationErrors.
Exemple
package validate
import (
"fmt"
"github.com/go-playground/validator/v10"
"testing"
)
type User struct {
Name string `validate:"contains=jack"` // le nom contient jack
Age int `validate:"gte=18"` // supérieur ou égal à 17 ans
Address string `valiate:"endwith=市"` // se termine par 市 (ville)
}
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()) // espace de noms
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)
}Sortie
User.Age
Age
User.Age
Age
gte
gte
int
int
17
18
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tagValidation de map
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}Valide les paires clé-valeur via un tag map.
Exemple
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)
}Sortie
map[age:Key: '' Error:Field validation for '' failed on the 'gte' tag name:Key: '' Error:Field validation for '' failed on the 'contains' tag]Validation de slice
Valide un slice de chaînes, le tag avant dive est pour valider le slice, le tag après dive est pour valider les valeurs dans le slice, les slices imbriqués fonctionnent de la même manière, utilisez autant de dive qu'il y a de dimensions.
func TestSlice1(t *testing.T) {
list := []string{"jack", "mike", "lisa", "golang"}
err := validator.New().Var(list, "max=5,dive,contains=a,min=5") // longueur maximale du slice est 5, les éléments doivent contenir le caractère a, et longueur minimale de 5
fmt.Println(err)
}Sortie
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' tagValidation de structure pour chaque utilisateur dans le 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" signifie validation en profondeur, lorsque l'élément est une structure, la validation de structure est automatiquement effectuée
fmt.Println(err)
}Sortie
Key: '[0].Age' Error:Field validation for 'Age' failed on the 'gte' tagValidation de variable
Assez simple et facile à comprendre, pas besoin d'explications supplémentaires.
Exemple 1
func TestVar(t *testing.T) {
name := "jack"
err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") // longueur max 5, longueur min 1, contient la lettre a, se termine par la lettre l
fmt.Println(err)
}Sortie
Key: '' Error:Field validation for '' failed on the 'endswith' tagExemple 2
func TestVar1(t *testing.T) {
age := 18
err := validator.New().Var(age, "gte=19")
fmt.Println(err)
}Sortie
Key: '' Error:Field validation for '' failed on the 'gte' tagTIP
La méthode Var peut valider les types incluant les structures, variables, slices, maps, il faut l'utiliser raisonnablement avec le tag dive.
Validation de champ
Les paramètres de validation de champ ne sont plus des types de base, mais des noms de champs de structure, qui peuvent être les propres champs de la structure ou des champs de structures imbriquées.
type Password struct {
FirstPassword string `validate:"eqfield=SecondPassword"` // valide que les deux mots de passe saisis sont égaux
SecondPassword string
}
type RegisterUser struct {
Username string `validate:"necsfield=Password.FirstPassword"` // lors de l'inscription, pour des raisons de sécurité, interdit que le mot de passe et le nom d'utilisateur soient identiques
Password Password
}
func TestCrossStructFieldValidate(t *testing.T) {
validate = validator.New()
// Échec
fmt.Println(validate.Struct(RegisterUser{
Username: "gopher",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gophers",
},
}))
// Succès
fmt.Println(validate.Struct(RegisterUser{
Username: "gophers",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gopher",
},
}))
}Sortie
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
Lors de l'utilisation de la validation de champ, lorsque le champ ou la structure spécifié comme paramètre du Tag n'existe pas, il sera directement jugé comme échec de validation, par exemple :
type Password struct {
FirstPassword string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
SecondPassword string
}Pour ce genre de faute de frappe, il est difficile de la détecter, et lors de la validation, elle ne sera affichée que sous forme d'échec de validation, il faut y faire très attention.
Avancé
Ensuite, nous expliquerons quelques techniques d'utilisation avancées et plus d'opérations personnalisées.
Alias personnalisés
Parfois, pour un champ, il y a beaucoup de tags de validation, lorsque vous voulez les réutiliser pour un autre champ, vous pourriez faire un copier-coller, mais ce n'est pas la meilleure solution, une meilleure méthode est d'enregistrer un alias pour améliorer la réutilisabilité, regardez l'exemple suivant :
var validate *validator.Validate
const PERSON_NAME_RULES = "max=10,min=1,contains=jack"
func TestAlias(t *testing.T) {
validate = validator.New()
// Enregistrer l'alias
validate.RegisterAlias("namerules", PERSON_NAME_RULES)
type person struct {
FirstName string `validate:"namerules"` // Utiliser l'alias
LastName string `validate:"namerules"`
}
err := validate.Struct(person{
FirstName: "",
LastName: "",
})
fmt.Println(err)
}Sortie
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' tagFonction de validation personnalisée
Bien que les tags de validation fournis par le composant soient suffisants pour l'utilisation de base, parfois pour certains besoins particuliers, vous devez définir votre propre logique, Validator nous fournit les API pertinentes pour personnaliser les fonctions de validation. Regardons d'abord un exemple :
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"
}Crée une fonction qui vérifie si la valeur du champ est égale à "666", et son Tag correspondant est is666, la sortie est la suivante
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tagTIP
Si le Tag enregistré existe déjà, il sera écrasé par le nouveau, ce qui signifie que vous pouvez "réécrire" la logique de validation par défaut des Tags.
Fonction de validation de type personnalisée
La fonction de validation de type est spécifiquement pour un certain type, généralement utilisée pour certains types non de base, elle peut aussi écraser la validation des types de base par défaut, regardez l'exemple suivant :
type Address struct {
name string
}
func TestCustomTypeValidate(t *testing.T) {
validate = validator.New()
validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // Enregistrer la fonction de validation de type et le type correspondant
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 {
// Gestion d'erreur
if address.name == "" {
return address.name
}
return value // Retourner le champ signifie que la validation est correcte
}
return nil
}Sortie
Key: 'Example.Address' Error:Field validation for 'Address' failed on the 'required' tag
<nil>TIP
Enregistrer plusieurs types à une seule fonction fonctionne de la même manière.
Fonction de validation de structure personnalisée
La différence avec la fonction de validation de structure est que les autres fonctions prennent des champs en paramètres, tandis que cette fonction prend une structure en paramètre, regardez l'exemple suivant :
type People struct {
FirstName string
LastName string
}
func TestCustomStructLevel(t *testing.T) {
validate = validator.New()
validate.RegisterStructValidation(PeopleValidate, People{}) // Enregistrement de même type, peut aussi accepter plusieurs structures
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", "", "")
}
}Sortie
Key: 'People.FirstName' Error:Field validation for 'FirstName' failed on the '' tag
Key: 'People.LastName' Error:Field validation for 'LastName' failed on the '' tagMultilingue
Composant traducteur
go get github.com/go-playground/universal-translatorComposant de locale
go get github.com/go-playground/localesLa langue par défaut du validateur est l'anglais, et lors du développement de projets, nous pouvons avoir besoin de plus d'une langue, c'est là que nous avons besoin des composants d'internationalisation multilingue, regardez l'exemple suivant :
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"` // le nom contient jack
Age int `validate:"gte=18"` // supérieur ou égal à 17 ans
Address string `validate:"endswith=市"` // se termine par 市 (ville)
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
func TestTranslate(t *testing.T) {
zh := zh.New()
// Le premier est le fallback, les suivants sont les langues supportées, il peut y en avoir plusieurs
uni = ut.New(zh, zh)
// La langue est généralement obtenue à partir de l'en-tête HTTP Accept-Language
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // Enregistrer le traducteur par défaut
}
err := validate.Struct(User{
Name: "",
Age: 0,
Address: "",
})
fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}Sortie
map[User.Address:Address doit se terminer par '市' User.Age:Age doit être supérieur ou égal à 18 User.Name:Name doit contenir 'jack']Vous pouvez aussi traduire chaque erreur individuellement
for _, fieldError := range err.(validator.ValidationErrors) {
fmt.Println(fieldError.Translate(trans))
}Sortie
Name doit contenir 'jack'
Age doit être supérieur ou égal à 18
Address doit se terminer par '市'On peut voir que la valeur de retour est une map, la traduction de base des messages d'erreur est faite, mais ce n'est pas encore suffisant pour une utilisation en production, nous devons embellir les messages d'erreur pour mieux interagir avec les clients ou le frontend.
type User struct {
Name string `validate:"contains=jack" label:"姓名"` // le nom contient jack
Age int `validate:"gte=18" label:"年龄"` // supérieur ou égal à 17 ans
Address string `validate:"endswith=市" label:"地址"` // se termine par 市
Sex string `validate:"required" label:"性别"`
}D'abord, définir un Tag personnalisé label, sa valeur est le nom chinois du champ, puis enregistrer un TagNameFunc via le validateur, son rôle est de remplacer le nom original lors de l'obtention du nom du champ. Dans le fichier errors.go, la méthode Filed() string a un commentaire qui dit : "Le nom de champ avec le tag a la priorité sur le nom réel du champ", donc ensuite, lors d'une erreur, vous pouvez utiliser le nom chinois personnalisé pour remplacer le mot anglais. TagNameFunc est la suivante :
// Nous avons ajouté un tag personnalisé, ce tag est utilisé pour donner un nom chinois au champ de structure, il remplacera le nom du champ original
func CustomTagNameFunc(field reflect.StructField) string {
label := field.Tag.Get("label")
if len(label) == 0 {
return field.Name
}
return label
}Enfin, l'enregistrer
validate.RegisterTagNameFunc(CustomTagNameFunc)Exécuter à nouveau, sortie
姓名 doit contenir 'jack'
年龄 doit être supérieur ou égal à 18
地址 doit se terminer par '市'Mais ce n'est toujours pas suffisant pour être retourné comme message d'erreur au frontend, nous devons formater le message en JSON ou tout autre format adapté à la transmission de messages, vous pourriez penser à sérialiser directement la map en JSON, c'est une solution, mais vous pourriez obtenir le résultat suivant :
{
"User.地址": "地址 doit se terminer par '市'",
"User.姓名": "姓名 doit contenir 'back'",
"User.年龄": "年龄 doit être supérieur ou égal à 18",
"User.性别": "性别 est un champ obligatoire"
}En traitant la clé de la map, on obtient le résultat suivant :
{
"地址": "地址 doit se terminer par '市'",
"姓名": "姓名 doit contenir 'back'",
"年龄": "年龄 doit être supérieur ou égal à 18",
"性别": "性别 est un champ obligatoire"
}Cependant, il n'est pas recommandé de retourner ce type d'information au frontend, nous pouvons formater en une seule chaîne comme message de retour
姓名 doit contenir 'back', 年龄 doit être supérieur ou égal à 18, 地址 doit se terminer par '市', 性别 est un champ obligatoire,Code complet
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:"姓名"` // le nom contient jack
Age int `validate:"gte=18" label:"年龄"` // supérieur ou égal à 17 ans
Address string `validate:"endswith=市" label:"地址"` // se termine par 市
Sex string `validate:"required" label:"性别"`
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
// Nous avons ajouté un tag personnalisé, ce tag est utilisé pour donner un nom chinois au champ de structure, il remplacera le nom du champ original
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 langue est généralement obtenue à partir de l'en-tête HTTP Accept-Language
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // Enregistrer le traducteur par défaut
}
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()
}Enfin, si vous trouvez les messages d'erreur trop froids et souhaitez quelque chose de plus humain, vous pouvez réécrire les messages d'erreur pour des tags spécifiques, cela nécessite d'utiliser la méthode RegisterTranslation, et aussi deux types de fonctions, RegisterTranslationsFunc qui est responsable d'enregistrer le modèle de traduction pour le Tag correspondant, et TranslationFunc qui est responsable de traiter le modèle pour obtenir le contenu de traduction final. Voici un exemple avec required :
func requiredOverrideRegister(ut ut.Translator) error { // Cette fonction sert à enregistrer le modèle de traduction
return ut.Add("required", "{}是一个必须填写的字段", true) // {} est un espace réservé, true signifie s'il faut écraser le modèle existant
}
func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // Cette fonction sert à traduire le contenu
t, _ := ut.T("required", fe.Field()) // Les paramètres peuvent être multiples, selon le nombre d'espaces réservés dans le modèle enregistré pour le Tag correspondant
return t
}Enfin, enregistrez-le
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)Résultat
姓名 doit contenir 'back', 年龄 doit être supérieur ou égal à 18, 地址 doit se terminer par '市', 性别 est un champ obligatoire,Fichiers de langue
En fait, enregistrer un par un en code est très fastidieux, universal-translator fournit un moyen de faire de la traduction via des fichiers de configuration JSON : universal-translator/examples/full-with-files at master · go-playground/universal-translator (github.com)
func TestFilei18n(t *testing.T) {
validate = validator.New()
zh := zh.New()
universalTranslator := ut.New(zh, zh)
translator, _ := universalTranslator.GetTranslator(zh.Locale())
zh_trans.RegisterDefaultTranslations(validate, translator)
er := universalTranslator.Import(ut.FormatJSON, "./zh.json") // Il est recommandé d'importer après l'enregistrement pour écraser les Tags existants
if er != nil {
log.Fatal(er)
}
type Gopher struct {
Language string `validate:"required"`
}
err := validate.Struct(Gopher{
"",
})
fmt.Println(err.(validator.ValidationErrors).Translate(translator))
}Fichier JSON
[
{
"locale": "zh",
"key": "required",
"trans": "这是一个十分重要的字段{0},你必须填写它",
"override": true
}
]Sortie
map[Gopher.Language:这是一个十分重要的字段Language,你必须填写它]TIP
universal-translator a beaucoup de pièges lors de l'utilisation, si vous voulez écraser un Tag existant, type et rule peuvent ne pas être remplis, car la configuration originale ne les a pas remplis non plus, il vaut mieux rester cohérent. Si vous remplissez un type, la configuration sera ajoutée à la map correspondante, si c'est Cardinal ou un autre type et que rule est configuré avec one ou similaire, alors vous devez faire la configuration correspondante localement pour pouvoir l'utiliser normalement, sinon il y aura une erreur.
