Je dois valider qu'une valeur de structure est correcte et cela signifie que je dois vérifier chaque champ individuellement, ce qui est facile pour un petit nombre de petites structures, mais je me demandais s'il y avait une meilleure façon de le faire. Voici comment je le fais en ce moment.
type Event struct {
Id int
UserId int
Start time.Time
End time.Time
Title string
Notes string
}
func (e Event) IsValid() error {
if e.Id <= 0 {
return errors.New("Id must be greater than 0")
}
if e.UserId <= 0 {
return errors.New("UserId must be greater than 0")
}
if e.End <= e.Start {
return errors.New("End must be after Start")
}
if e.Start < time.Now() {
return errors.New("Cannot create events in the past")
}
if e.Title == "" {
return errors.New("Title cannot be empty")
}
return nil
}
Est-ce la manière idiomatique de valider les valeurs des champs dans une structure? Cela semble encombrant.
Je ne vois pas d'autre moyen de faire cela rapidement. Mais j'ai trouvé un package qui peut vous aider avec ceci: https://github.com/go-validator/validator
Le fichier README donne cet exemple:
type NewUserRequest struct {
Username string `validator:"min=3,max=40,regexp=^[a-zA-Z]$"`
Name string `validator:"nonzero"`
Age int `validator:"min=21"`
Password string `validator:"min=8"`
}
nur := NewUserRequest{Username: "something", Age: 20}
if valid, errs := validator.Validate(nur); !valid {
// values not valid, deal with errors here
}
En procédant ainsi, vous finirez par écrire beaucoup de code en double pour chacun de vos modèles.
L'utilisation d'une bibliothèque avec des balises comporte ses propres avantages et inconvénients. Il est parfois facile de commencer, mais vous rencontrez plus tard les limites de la bibliothèque.
Une approche possible consiste à créer un "Validateur" dont la seule responsabilité est de garder une trace des erreurs possibles dans un objet.
Un bout très approximatif de cette idée:
http://play.golang.org/p/buBUzk5z6I
package main
import (
"fmt"
"time"
)
type Event struct {
Id int
UserId int
Start time.Time
End time.Time
Title string
Notes string
}
type Validator struct {
err error
}
func (v *Validator) MustBeGreaterThan(high, value int) bool {
if v.err != nil {
return false
}
if value <= high {
v.err = fmt.Errorf("Must be Greater than %d", high)
return false
}
return true
}
func (v *Validator) MustBeBefore(high, value time.Time) bool {
if v.err != nil {
return false
}
if value.After(high) {
v.err = fmt.Errorf("Must be Before than %v", high)
return false
}
return true
}
func (v *Validator) MustBeNotEmpty(value string) bool {
if v.err != nil {
return false
}
if value == "" {
v.err = fmt.Errorf("Must not be Empty")
return false
}
return true
}
func (v *Validator) IsValid() bool {
return v.err != nil
}
func (v *Validator) Error() string {
return v.err.Error()
}
func main() {
v := new(Validator)
e := new(Event)
v.MustBeGreaterThan(e.Id, 0)
v.MustBeGreaterThan(e.UserId, 0)
v.MustBeBefore(e.End, e.Start)
v.MustBeNotEmpty(e.Title)
if !v.IsValid() {
fmt.Println(v)
} else {
fmt.Println("Valid")
}
}
package main
import (
"fmt"
"time"
)
type Event struct {
Id int
UserId int
Start time.Time
End time.Time
Title string
Notes string
}
type Validator struct {
err error
}
func (v *Validator) MustBeGreaterThan(high, value int) bool {
if v.err != nil {
return false
}
if value <= high {
v.err = fmt.Errorf("Must be Greater than %d", high)
return false
}
return true
}
func (v *Validator) MustBeBefore(high, value time.Time) bool {
if v.err != nil {
return false
}
if value.After(high) {
v.err = fmt.Errorf("Must be Before than %v", high)
return false
}
return true
}
func (v *Validator) MustBeNotEmpty(value string) bool {
if v.err != nil {
return false
}
if value == "" {
v.err = fmt.Errorf("Must not be Empty")
return false
}
return true
}
func (v *Validator) IsValid() bool {
return v.err != nil
}
func (v *Validator) Error() string {
return v.err.Error()
}
func main() {
v := new(Validator)
e := new(Event)
v.MustBeGreaterThan(e.Id, 0)
v.MustBeGreaterThan(e.UserId, 0)
v.MustBeBefore(e.End, e.Start)
v.MustBeNotEmpty(e.Title)
if !v.IsValid() {
fmt.Println(v)
} else {
fmt.Println("Valid")
}
}
Vous pouvez ensuite créer votre méthode Validate et utiliser le même code:
func (e *Event) IsValid() error {
v := new(Validator)
v.MustBeGreaterThan(e.Id, 0)
v.MustBeGreaterThan(e.UserId, 0)
v.MustBeBefore(e.End, e.Start)
v.MustBeNotEmpty(e.Title)
return v.IsValid()
}
Pour aider ceux qui recherchent une autre bibliothèque de validation, j'ai créé le fichier suivant: https://github.com/bluesuncorp/validator
Il aborde des problèmes que d'autres plugins n'ont pas encore implémentés et que d'autres dans ce fil ont mentionnés, comme:
Inspiré par plusieurs autres projets, y compris la réponse acceptée de go-validator/validator
J'écrirais du code explicite plutôt que d'utiliser une bibliothèque de validation. L’avantage d’écrire votre propre code est que vous n’ajoutez pas de dépendance supplémentaire, vous n'avez pas besoin d'apprendre une ADSL et vous pouvez vérifier les propriétés de vos structures dépendantes de plusieurs champs ).
Pour réduire le passe-temps, je pourrais extraire une fonction qui ajoute un message d'erreur à une tranche d'erreurs dans le cas où un invariant est faux.
func check(ea *[]string, c bool, errMsg string, ...args) {
if !c { *ea = append(*ea, fmt.Sprintf(errMsg, ...args)) }
}
func (e *Event) Validate() error {
var ea []string
check(&ea, e.ID >= 0, "want positive ID, got %d", e.ID)
check(&ea, e.Start < e.End, "want start < end, got %s >= %s", e.Start, e.End)
...
if len(ea) > 0 {
return errors.New(strings.Join(ea, ", "))
}
return nil
}
Cela retourne tous les moyens par lesquels la structure échoue à la validation plutôt que la première, ce qui peut être ou ne pas être ce que vous voulez.
Peut-être que vous pouvez essayer valider . Avec cette bibliothèque, vous pouvez valider votre structure comme ceci:
package main
import (
"fmt"
"time"
v "github.com/RussellLuo/validating"
)
type Event struct {
Id int
UserId int
Start time.Time
End time.Time
Title string
Notes string
}
func (e *Event) Schema() v.Schema {
return v.Schema{
v.F("id", &e.Id): v.Gt(0),
v.F("user_id", &e.UserId): v.Gt(0),
v.F("start", &e.Start): v.Gte(time.Now()),
v.F("end", &e.End): v.Gt(e.Start),
v.F("title", &e.Title): v.Nonzero(),
v.F("notes", &e.Notes): v.Nonzero(),
}
}
func main() {
e := Event{}
err := v.Validate(e.Schema())
fmt.Printf("err: %+v\n", err)
}
Une approche différente qui ne nécessite pas de réflexion et renvoie la première erreur utilise quelque chose comme this
:
type Event struct {
Id int
UserId int
Start time.Time
End time.Time
Title string
Notes string
}
func (e *Event) Validate() error {
return Check(
Cf(e.Id <= 0, "Expected ID <= 0, got %d.", e.Id),
Cf(e.Start.UnixNano() > e.End.UnixNano(), "Expected start < end, got %s >= %s.", e.Start, e.End),
)
}
type C struct {
Check bool
Error error
}
func Cf(chk bool, errmsg string, params ...interface{}) C {
return C{chk, fmt.Errorf(errmsg, params...)}
}
func Check(args ...C) error {
for _, c := range args {
if !c.Check {
return c.Error
}
}
return nil
}
func main() {
a := Event{Id: 1, Start: time.Now()}
b := Event{Id: -1}
fmt.Println(a.Validate(), b.Validate())
}
Je pense que c'est une meilleure façon!
import (
"fmt"
"github.com/bytedance/go-tagexpr/validator"
)
func Example() {
var vd = validator.New("vd")
type InfoRequest struct {
Name string `vd:"($!='Alice'||(Age)$==18) && regexp('\\w')"`
Age int `vd:"$>0"`
}
info := &InfoRequest{Name: "Alice", Age: 18}
fmt.Println(vd.Validate(info) == nil)
}
https://github.com/bytedance/go-tagexpr/tree/master/validator
La méthode que vous décrivez est certainement la façon la plus simple de le faire.
Vous pouvez utiliser la réflexion avec les balises de champ struct pour effectuer une validation automatisée. Mais cela nécessitera d'écrire une bibliothèque qui le fait pour vous. L'avantage est qu'une fois que vous avez écrit la bibliothèque de validation, vous pouvez la réutiliser avec n'importe quelle structure.
Voici un exemple d'utilisation de ce code:
type Person struct {
Name string `minlength:"3" maxlength:"20"`
Age int `min:"18" max:"80"`
}
Vous créez une instance de ce type et la transmettez dans votre code de validation . Il utilisera les règles des balises de champ pour valider les valeurs de champ.
Il y a probablement quelques bibliothèques qui font ce genre de choses pour vous, mais je ne sais pas si elles fonctionnent bien ou si elles sont toujours entretenues.