[英]Idiomatic way to validate structs
我需要验证结构值是否正确,这意味着我需要单独检查每个字段,这对于少数小型结构来说很容易,但我想知道是否有更好的方法来做到这一点。 这就是我现在的做法。
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
}
这是验证结构中字段值的惯用方法吗? 看起来很麻烦。
我没有看到任何其他方法可以快速做到这一点。 但是我找到了一个可以帮助您解决此问题的 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
}
这样做最终会为每个模型编写大量重复代码。
使用带有标签的库有其自身的优点和缺点。 有时很容易开始,但在路上你会遇到图书馆的限制。
一种可能的方法是创建一个“验证器”,它的唯一职责是跟踪对象内部可能出现的错误。
这个想法的一个非常近似的存根:
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()
}
为了帮助可能正在寻找另一个验证库的其他人,我创建了以下https://github.com/bluesuncorp/validator
它解决了其他插件尚未实现的一些问题,该线程中的其他人已经提到,例如:
受其他几个项目的启发,包括 go-validator/validator 的公认答案
我会编写显式代码而不是使用验证库。 编写自己的代码的好处是不需要添加额外的依赖,不需要学习 DSL,并且可以检查依赖于多个字段的结构的属性(例如 start < 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
}
这将返回结构验证失败的所有方式,而不仅仅是第一个,这可能是也可能不是您想要的。
也许你可以试试验证。 使用这个库,你可以像这样验证你的结构:
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)
}
不需要反射并在第一个错误时返回的另一种方法是使用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())
}
您描述的方法当然是最直接的方法。
您可以使用带有struct 字段标记的反射来进行自动验证。 但这需要编写一个为您执行此操作的库。 好处是一旦你编写了验证库,你就可以在任何结构中重用它。
使用此代码的方法示例如下:
type Person struct {
Name string `minlength:"3" maxlength:"20"`
Age int `min:"18" max:"80"`
}
您将创建此类型的实例并将其传递到您的验证代码中。 它将使用字段标签中的规则来验证字段值。
可能有一些图书馆可以为您做这种事情,但我不确定它们的工作情况或是否仍在维护。
我认为这是一个更好的方法!
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.