Skip to content

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)

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

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

Import

go
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

go
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

TagDescription
eqcsfieldDans une structure séparée, valide si la valeur du champ actuel est égale au champ spécifié par la valeur du paramètre
eqfieldValide si la valeur du champ actuel est égale au champ spécifié par la valeur du paramètre
fieldcontainsValide si la valeur du champ actuel contient le champ spécifié par la valeur du paramètre
fieldexcludesValide si la valeur du champ actuel ne contient pas le champ spécifié par la valeur du paramètre
gtcsfieldDans 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
gtecsfieldDans 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
gtefieldValide si la valeur du champ actuel est supérieure ou égale au champ spécifié par la valeur du paramètre
gtfieldValide si la valeur du champ actuel est supérieure au champ spécifié par la valeur du paramètre
ltcsfieldDans 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
ltecsfieldDans 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
ltefieldValide si la valeur du champ actuel est inférieure ou égale au champ spécifié par la valeur du paramètre
ltfieldValide si la valeur du champ actuel est inférieure au champ spécifié par la valeur du paramètre
necsfieldValide 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
nefieldValide si la valeur du champ actuel n'est pas égale au champ spécifié par la valeur du paramètre

Réseau

TagDescription
cidrRoutage Inter-Domaine Sans Classe CIDR
cidrv4Routage Inter-Domaine Sans Classe CIDRv4
cidrv6Routage Inter-Domaine Sans Classe CIDRv6
datauriURI de données
fqdnNom de domaine pleinement qualifié (FQDN)
hostnameNom d'hôte RFC 952
hostname_portValidation de champ généralement utilisée pour les adresses de socket, combinaison <dns>:<port>
hostname_rfc1123Nom d'hôte RFC 952
ipAdresse IP
ip4_addrAdresse IPv4
ip6_addrAdresse IPv6
ip_addrAdresse IP
ipv4Adresse IPv4
ipv6Adresse IPv6
macAdresse MAC, aussi appelée adresse LAN
tcp4_addrAdresse TCP4
tcp6_addrAdresse TCPv6
tcp_addrAdresse TCP
udp4_addrAdresse UDPv4
udp6_addrAdresse UDPv6
udp_addrAdresse UDP
unix_addrAdresse de point de terminaison de socket Unix
uriURI
urlURL
url_encodedEncodage URI
urn_rfc2141URN RFC 2141

Chaînes de caractères

TagDescription
alphaValide si la valeur du champ actuel est une lettre valide
alphanumValide si la valeur du champ actuel est une valeur alphanumérique valide
alphanumunicodeValide si la valeur du champ actuel est une valeur alphanumérique unicode valide
alphaunicodeValide si la valeur du champ actuel est une lettre unicode valide
asciiValide si la valeur du champ est un caractère ASCII valide
booleanValide si la valeur du champ actuel est une valeur booléenne valide ou peut être convertie en toute sécurité en booléen
containsValide si la valeur du champ contient le texte spécifié dans le paramètre
containsanyValide si la valeur du champ contient l'un des caractères spécifiés dans le paramètre
containsruneValide si la valeur du champ contient le rune spécifié dans le paramètre
endsnotwithValide si la valeur du champ ne se termine pas par le texte spécifié dans le paramètre
endswithValide si la valeur du champ se termine par le texte spécifié dans le paramètre
excludesValide si la valeur du champ ne contient pas le texte spécifié dans le paramètre
excludesallValide si la valeur du champ ne contient aucun des caractères spécifiés dans le paramètre
excludesruneValide si la valeur du champ ne contient pas le caractère spécifié dans le paramètre
lowercaseValide si la valeur du champ actuel est une chaîne en minuscules
multibyteValide si la valeur du champ contient des caractères multi-octets
numberValide si la valeur du champ actuel est un nombre valide
numericValide si la valeur du champ actuel est une valeur numérique
printasciiValide si la valeur du champ est un caractère ASCII imprimable valide
startsnotwithValide si la valeur du champ ne commence pas par le texte spécifié dans le paramètre
startswithValide si la valeur du champ commence par le texte spécifié dans le paramètre
uppercaseValide si la valeur du champ actuel est une chaîne en majuscules

Formatage

TagDescription
base64Chaîne Base64
base64urlChaîne Base64URL
bicValide si la valeur du champ actuel est un code BIC valide (code SWIFT) défini dans ISO 9362
bcp47_language_tagValide si la valeur du champ actuel est une étiquette de langue conforme à BCP47
btc_addrValide si la valeur du champ est une adresse BTC valide
btc_addr_bech32Valide si la valeur du champ est une adresse BTC bech32 valide
credit_cardValide si la valeur du champ actuel est un numéro de carte de crédit valide
datetimeValide si la valeur du champ actuel est une chaîne de date/heure valide
e164Valide si la valeur du champ actuel est un numéro de téléphone au format e.164 valide
emailValide si la valeur du champ actuel est une adresse email valide
eth_addrValide si la valeur du champ est une adresse Ethereum valide
hexadecimalValide si la valeur du champ actuel est un hexadécimal valide
hexcolorValide si la valeur du champ actuel est une couleur hexadécimale valide
hslValide si la valeur du champ actuel est une couleur HSL valide
hslaValide si la valeur du champ actuel est une couleur HSLA valide
htmlValide si la valeur du champ actuel est un HTML valide
html_encodedValide si la valeur du champ actuel est un encodage HTML valide
isbnValide si la valeur du champ est un ISBN (International Standard Book Number) v10 ou v13 valide
isbn10Valide si la valeur du champ est un ISBN v10 (International Standard Book Number) valide
isbn13Valide si la valeur du champ est un ISBN v13 (International Standard Book Number) valide
iso3166_1_alpha2Valide si la valeur du champ actuel est un code pays iso3166-1 alpha-2 valide
iso3166_1_alpha3Valide si la valeur du champ actuel est un code pays iso3166-1 alpha-3 valide
iso3166_1_alpha_numericValide si la valeur du champ actuel est un code pays alphanumérique iso3166-1 valide
iso3166_2Valide si la valeur du champ actuel est un code de région de pays valide (ISO 3166-2)
iso4217Valide si la valeur du champ actuel est un code de devise valide (ISO 4217)
jsonValide si la valeur du champ actuel est une chaîne json valide
jwtValide si la valeur du champ actuel est une chaîne JWT valide
latitudeValide si la valeur du champ est une coordonnée de latitude valide
longitudeValide si la valeur du champ est une coordonnée de latitude valide
postcode_iso3166_alpha2Valide selon la valeur du code pays iso 3166 alpha 2
postcode_iso3166_alpha2_fieldValide via un champ qui représente la valeur du code pays dans iso 3166 alpha 2
rgbValide si la valeur du champ actuel est une couleur RGB valide
rgbaValide si la valeur du champ actuel est une couleur RGBA valide
ssnValide si la valeur du champ est un SSN valide
timezoneValide si la valeur du champ actuel est une chaîne de fuseau horaire valide
uuidValide si la valeur du champ est un UUID valide de n'importe quelle version
uuid3Valide si la valeur du champ est un UUID v3 valide
uuid3_rfc4122Valide si la valeur du champ est un UUID v3 RFC4122 valide
uuid4Valide si la valeur du champ est un UUID v4 valide
uuid4_rfc4122Valide si la valeur du champ est un UUID v4 RFC4122 valide
uuid5Valide si la valeur du champ est un UUID v5 valide
uuid5_rfc4122Valide si la valeur du champ est un UUID v5 RFC4122 valide
uuid_rfc4122Valide si la valeur du champ est un UUID RFC4122 valide de n'importe quelle version
md4Valide si la valeur du champ est un MD4 valide
md5Valide si la valeur du champ est un MD5 valide
sha256Valide si la valeur du champ est un SHA256 valide
sha384Valide si la valeur du champ est un SHA384 valide
sha512Valide si la valeur du champ est un SHA512 valide
ripemd128Valide si la valeur du champ est un PIPEMD128 valide
ripemd128Valide si la valeur du champ est un PIPEMD160 valide
tiger128Valide si la valeur du champ est un TIGER128 valide
tiger160Valide si la valeur du champ est un TIGER160 valide
tiger192Valide si la valeur du champ est un TIGER192 valide
semverValide si la valeur du champ actuel est une version semver valide définie dans la sémantique de version 2.0.0
ulidValide si la valeur du champ est un ULID valide

Comparaison

TagDescription
eqÉgal à
gtSupérieur à
gteSupérieur ou égal à
ltInférieur à
lteInférieur ou égal à
neDifférent de

Autres

TagDescription
dirRépertoire de fichiers
fileChemin de fichier
isdefaultValide si la valeur du champ actuel est une valeur statique par défaut
lenLongueur du champ
maxValeur maximale
minValeur minimale
oneofSi la valeur est l'une des valeurs listées
oimtemptySi le champ n'est pas défini, ignorer le champ
requiredValeur obligatoire
required_ifValide 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_unlessValide 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_withValide que le champ doit exister et ne pas être vide si l'un des champs spécifiés existe
required_with_allValide que le champ doit exister et ne pas être vide si tous les champs spécifiés existent
required_withoutValide que le champ doit exister et ne pas être vide si l'un des champs spécifiés n'existe pas
required_without_allValide que le champ doit exister et ne pas être vide si tous les champs spécifiés n'existent pas
excluded_ifValide 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_unlessValide 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_withValide que le champ peut ne pas exister ou être vide si l'un des champs spécifiés existe
excluded_with_allValide que le champ peut ne pas exister ou être vide si tous les champs spécifiés existent
excluded_withoutValide que le champ peut ne pas exister ou être vide si l'un des champs spécifiés n'existe pas
excluded_without_allValide que le champ peut ne pas exister ou être vide si tous les champs spécifiés n'existent pas
uniqueValide si chaque valeur arr, map, slice est unique

Alias

TagDescription
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_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

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

Utilisation

Ci-dessous, nous présenterons quelques utilisations de base de Validator ainsi que quelques exemples de code.

Singleton

go
var validate *validator.Validate

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

go
validate = validator.New()

Validation de structure

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

La 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

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

Validation de map

go
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

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

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.

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

Validation de structure pour chaque utilisateur dans le 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" 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' tag

Validation de variable

Assez simple et facile à comprendre, pas besoin d'explications supplémentaires.

Exemple 1

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

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

TIP

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.

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

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

go
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

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

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

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

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

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

TIP

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 :

go
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

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

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

Multilingue

Composant traducteur

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

Composant de locale

go get github.com/go-playground/locales

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

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"` // 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

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

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

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

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

json
{
  "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 :

json
{
  "地址": "地址 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

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:"姓名"` // 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 :

go
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

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

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") // 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

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.

Golang by www.golangdev.cn edit