[英]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.