[英]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:它解决了其他插件尚未实现的一些问题,该线程中的其他人已经提到,例如:
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
I would suggest you look up the Cue<\/a> Go integration, which covers many validation scenarios, and is especially interesting in that it is an abstract validator mapping to various languages, thereby allowing you to share validations among various parts of your system regardless of language, avoiding the issues with diverging language-based validators.我建议您查看Cue<\/a> Go 集成,它涵盖了许多验证场景,并且特别有趣的是它是一个抽象的验证器映射到各种语言,从而允许您在系统的各个部分之间共享验证,而无需考虑语言,避免基于不同语言的验证器的问题。 Think, for example, of sharing your validators among JS<\/a> and Go code例如,考虑在JS<\/a>和 Go 代码之间共享验证器
"
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.