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

adr*_*ers 44 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
}
Run Code Online (Sandbox Code Playgroud)

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

jul*_*enc 40

我没有看到任何其他方法快速做到这一点.但是我找到了一个可以帮助你的go软件包: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
}
Run Code Online (Sandbox Code Playgroud)

  • 我写了这个包.谢谢你的分享.原始提问者提到的一件事是无法比较两个字段的能力(`e.End> e.Start`).我可以看到它如何有用.我想我可能会实现类似的东西. (22认同)
  • 看起来像@RobertoSelbach [添加了跨领域验证](https://github.com/go-playground/validator/blob/v9/doc.go#L47)。 (2认同)

fab*_*ioM 15

这样,您最终会为每个模型编写大量重复代码.

使用带有标签的库有其自身的优缺点.有时很容易开始,但在路上你遇到了库限制.

一种可能的方法是创建一个"验证器",其唯一的责任是跟踪对象内部可能的错误.

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

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以创建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()
}
Run Code Online (Sandbox Code Playgroud)

  • 关于库限制,这是您需要处理的_any_库,而不仅仅是验证.这就是开源非常酷的原因,你可以改进图书馆并与世界分享.也就是说,我也喜欢你的方法,我认为它可能会成为一个有用的包(它会在最终的限制中运行,但我们已经完成了这个.) (2认同)

dea*_*arn 11

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

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

  • 返回所有验证错误
  • 每个字段多次验证
  • 交叉场验证 开始>结束日期

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


Pau*_*kin 5

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

为了减少样板,我可能会提取一个函数,在不变量为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
 }
Run Code Online (Sandbox Code Playgroud)

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