validation - array - golang validate string
¿Forma idiomática de validar estructuras en Go? (7)
Necesito validar que un valor de estructura es correcto y esto significa que debo verificar cada campo individualmente, lo cual es fácil para un pequeño número de estructuras pequeñas, pero me preguntaba si hay una mejor manera de hacerlo. Así es como lo estoy haciendo ahora mismo.
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
}
¿Es esta la manera idiomática de validar los valores de los campos en una estructura? Parece engorroso.
El método que describe es sin duda la forma más directa de hacerlo.
Puede usar la reflexión con las etiquetas de campo struct para realizar la validación automática. Pero esto requerirá escribir una biblioteca que haga esto por ti. Lo bueno es que una vez que haya escrito la biblioteca de validación, puede reutilizarla con cualquier estructura.
Un ejemplo de una forma de usar este código sería:
type Person struct {
Name string `minlength:"3" maxlength:"20"`
Age int `min:"18" max:"80"`
}
Crearía una instancia de este tipo y la pasaría a su código de validación. Usaría las reglas en las etiquetas de campo para validar los valores de campo.
Probablemente hay algunas bibliotecas que hacen este tipo de cosas por usted, pero no estoy seguro de qué tan bien funcionan o si aún se mantienen.
Escribiría código explícito en lugar de usar una biblioteca de validación. La ventaja de escribir su propio código es que no agrega una dependencia adicional, no necesita aprender una DSL, y puede verificar las propiedades de sus estructuras que dependen de múltiples campos (por ejemplo, que comienzan <fin ).
Para reducir la repetición, podría extraer una función que agregue un mensaje de error a una porción de errores en el caso de que un invariante sea falso.
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
}
Esto devuelve todas las formas en que la estructura falla la validación en lugar de solo la primera, que puede ser o no lo que usted desea.
No veo otra forma de hacer esto rápidamente. Pero encontré un paquete go que puede ayudarlo con esto: https://github.com/go-validator/validator
El archivo README da este ejemplo:
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
}
Para ayudar a cualquier otra persona que esté buscando otra biblioteca de validación, creé el siguiente github.com/bluesuncorp/validator
Se abordan algunos problemas que otros complementos no han implementado todavía que otros en este hilo han mencionado, tales como:
- Devolución de todos los errores de validación
- validaciones múltiples por campo
- validación de campo cruzado ex. Inicio> Fecha de finalización
Inspirado en varios otros proyectos, incluida la respuesta aceptada de go-validator / validator
Quizás puedas dar una oportunidad de validating . Con esta biblioteca, puede validar su estructura de esta manera:
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)
}
Si lo hace, terminará escribiendo una gran cantidad de código duplicado para cada uno de su modelo.
Usar una biblioteca con etiquetas viene con sus propios pros y contras. A veces es fácil comenzar, pero en el camino usted llega a las limitaciones de la biblioteca.
Un posible enfoque es crear un "Validador" que su única responsabilidad es hacer un seguimiento de los posibles errores dentro de un objeto.
Un resumen muy aproximado de esta idea:
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")
}
}
Luego puede crear su método Validate y usar el mismo código:
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()
}
Un enfoque diferente que no necesita reflexión y regresa en el primer error es usar algo como 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())
}