在Go中验证结构的惯用方法?

时间:2014-05-30 12:47:50

标签: validation go

我需要验证结构值是否正确,这意味着我需要单独检查每个字段,这对于少量小结构很容易,但我想知道是否有更好的方法。以下是我现在正在做的事情。

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
}

这是验证结构中字段值的惯用方法吗?它看起来很麻烦。

8 个答案:

答案 0 :(得分:35)

我没有看到任何其他方法可以快速完成此操作。但是我找到了一个可以帮助你解决问题的方法:https://github.com/go-validator/validator

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
}

答案 1 :(得分:15)

这样做最终会为每个模型编写大量重复的代码。

使用带有标签的库有其自身的优缺点。有时候很容易开始,但是在你走向图书馆限制的道路上。

一种可能的方法是创建一个&#34;验证器&#34;它的唯一责任是跟踪对象内部可能存在的错误。

这个想法的一个非常近似的存根:

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

然后,您可以创建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()
}

答案 2 :(得分:9)

为了帮助其他可能正在寻找其他验证库的人,我创建了以下https://github.com/bluesuncorp/validator

它解决了其他插件尚未实现的问题,该线程中的其他人已经提到过,例如:

  • 返回所有验证错误
  • 每个字段的多个验证
  • 交叉场验证ex。开始&gt;结束日期

受到其他几个项目的启发,包括go-validator / validator的接受答案

答案 3 :(得分:5)

我编写显式代码而不是使用验证库。编写自己的代码的好处是,您不需要添加额外的依赖项,您不需要学习DSL,并且可以检查依赖于多个字段的结构的属性(例如,开始&lt; end)。

为了减少样板,我可能会提取一个函数,在不变量为假的情况下,会向一片错误添加错误消息。

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
 }

这将返回struct验证验证的所有方式,而不仅仅是第一个,这可能是你想要的,也可能不是。

答案 4 :(得分:3)

也许你可以试试validating。使用此库,您可以像这样验证结构:

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

答案 5 :(得分:1)

第一个错误不需要反射和返回的不同方法是使用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())
}

答案 6 :(得分:0)

您描述的方法当然是最直接的方法。

您可以使用struct field tags的反射来进行自动验证。但这需要编写一个为您执行此操作的库。好处是,一旦您编写了验证库,就可以将其重用于任何结构。

使用此代码的方法示例如下:

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

您将创建此类型的实例并将其传递给验证代码。 它将使用字段标记中的规则来验证字段值。

可能有一些图书馆会为你做这类事情,但我不确定它们的效果如何,或者它们是否仍在维护中。

答案 7 :(得分:0)

我认为这是一种更好的方法!

0:

https://github.com/bytedance/go-tagexpr/tree/master/validator