简体   繁体   English

"验证结构的惯用方法"

[英]Idiomatic way to validate structs

I need to validate that a struct value is correct and this means I need to check every field individually, which is easy for a small number of small structs but I was wondering if there's a better way to do it.我需要验证结构值是否正确,这意味着我需要单独检查每个字段,这对于少数小型结构来说很容易,但我想知道是否有更好的方法来做到这一点。 Here's how I'm doing it right now.这就是我现在的做法。

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
}

Is this the idiomatic way to validate the values of fields in a struct?这是验证结构中字段值的惯用方法吗? It looks cumbersome.看起来很麻烦。

I don't see any other way to do this quickly.我没有看到任何其他方法可以快速做到这一点。 But I found a go package which can help you with this: https://github.com/go-validator/validator但是我找到了一个可以帮助您解决此问题的 go 包: https : //github.com/go-validator/validator

The README file gives this example: README 文件给出了这个例子:

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
}

Doing that way you will end up writing a lot of duplicate code for each of your model.这样做最终会为每个模型编写大量重复代码。

Using a library with tags comes with its own pros and cons.使用带有标签的库有其自身的优点和缺点。 Sometimes is easy to start but down the road you hit the library limitations.有时很容易开始,但在路上你会遇到图书馆的限制。

One possible approach is to create a "Validator" that its only responsibility is to keep track of the possible errors inside an object.一种可能的方法是创建一个“验证器”,它的唯一职责是跟踪对象内部可能出现的错误。

A very approximate stub of this idea:这个想法的一个非常近似的存根:

http://play.golang.org/p/buBUzk5z6I 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")
    }
}

You can then create your Validate method and use the same code:然后,您可以创建您的 Validate 方法并使用相同的代码:

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

To help anyone else that may be looking for another validation library I created the following https://github.com/bluesuncorp/validator为了帮助可能正在寻找另一个验证库的其他人,我创建了以下https://github.com/bluesuncorp/validator

It addresses some issues that other plugins have not implemented yet that others in this thread had mentioned such as:它解决了其他插件尚未实现的一些问题,该线程中的其他人已经提到,例如:

  • Returning all validation errors返回所有验证错误
  • multiple validations per field每个字段多次验证
  • cross field validation ex.跨领域验证例如。 Start > End date开始 > 结束日期

Inspired by several other projects including the accepted answer of go-validator/validator受其他几个项目的启发,包括 go-validator/validator 的公认答案

I'd write explicit code rather than use a validation library.我会编写显式代码而不是使用验证库。 The advantage of writing your own code is that you don't add an extra dependency, you don't need to learn a DSL, and you can check properties of your structs that are dependent on multiple fields (for example, that start < end).编写自己的代码的好处是不需要添加额外的依赖,不需要学习 DSL,并且可以检查依赖于多个字段的结构的属性(例如 start < end )。

To cut down on the boilerplate, I might extract a function that adds an error message to a slice of errors in the case an invariant is false.为了减少样板文件,我可能会提取一个函数,在不变量为假的情况下将错误消息添加到错误片段中。

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
 }

This returns all ways the struct fails validation rather than just the first, which may or may not be what you want.这将返回结构验证失败的所有方式,而不仅仅是第一个,这可能是也可能不是您想要的。

Maybe you can give validating a try.也许你可以试试验证 With this library, you can validate your struct like this:使用这个库,你可以像这样验证你的结构:

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

A different approach that doesn't need reflection and returns on the first error is using something like this :不需要反射并在第一个错误时返回的另一种方法是使用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())
}

The method you describe is certainly the most straight forward way to do it.您描述的方法当然是最直接的方法。

You can use reflection with struct field tags to do automated validation.您可以使用带有struct 字段标记的反射来进行自动验证。 But this will require writing a library which does this for you.但这需要编写一个为您执行此操作的库。 The upside is that once you've written the validation library, you can reuse it with any struct.好处是一旦你编写了验证库,你就可以在任何结构中重用它。

An example of a way to use this code would be:使用此代码的方法示例如下:

type Person struct {
    Name string `minlength:"3" maxlength:"20"`
    Age  int    `min:"18" max:"80"`
}

You would create an instance of this type and pass it into your validation code.您将创建此类型的实例并将其传递到您的验证代码中。 It would use the rules in the field tags to validate the field values.它将使用字段标签中的规则来验证字段值。

There are probably a few libraries out there which do this sort of thing for you, but I am not sure how well they work or if they are still being maintained.可能有一些图书馆可以为您做这种事情,但我不确定它们的工作情况或是否仍在维护。

I think this is a better way!我认为这是一个更好的方法!

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 https://github.com/bytedance/go-tagexpr/tree/master/validator

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM