This is a creation in Article, where the information may have evolved or changed.
This article was also published in https://github.com/zhangyachen/zhangyachen.github.io/issues/125
Suppose we have the following structure:
type User struct { Id int Name string Bio string Email string }
We need to verify the legitimacy of the fields in the structure:
- The value of the ID is within a range.
- The length of name is within a certain range.
- The email format is correct.
We may be writing this:
user := User{ Id: 0, Name: "superlongstring", Bio: "", Email: "foobar",}if user.Id < 1 && user.Id > 1000 { return false}if len(user.Name) < 2 && len(user.Name) > 10 { return false}if !validateEmail(user.Email) { return false}
In this case, the code is redundant, and if the structure of the new field, you also need to modify the validation function and add an if judgment. This makes the code more redundant. We can use Golang's structtag to solve the above problems:
type User struct { Id int `validate:"number,min=1,max=1000"` Name string `validate:"string,min=2,max=10"` Bio string `validate:"string"` Email string `validate:"email"`}
validate:"number,min=1,max=1000"
Is Structtag. If you are unfamiliar with this, look at the following:
type User struct { Id int `json:"id"` Name string `json:"name"` Bio string `json:"about,omitempty"` Active bool `json:"active"` Admin bool `json:"-"` CreatedAt time.Time `json:"created_at"`}
Golang has written about the basic use of json:xxx
this usage, in fact, is json:xxx
a structtag, but this is Golang help you achieve a specific use of structtag. And validate:"number,min=1,max=1000"
It's our custom Structtag.
Implementation ideas
We define an interface Validator
that defines a method Validate
. Then define a specific validator such as, StringValidator
NumberValidator
EmailValidator
to implement the interface Validator
.
Why do we use interfaces here? Suppose we do not use interface code what will be written?
if tagIsOfNumber(){ validator := NumberValidator{}}else if tagIsOfString() { validator := StringValidator{}}else if tagIsOfEmail() { validator := EmailValidator{}}else if tagIsOfDefault() { validator := DefaultValidator{}}
In this case, the judgment logic cannot be written in a function, because the return value validator is different because of the structtag, and validator cannot be passed as a function parameter. And we define an interface, all validator to implement this interface, the above problems can be solved, and the logic is more clear and compact.
On the use of interfaces can be seen in the standard library IO Writer,writer is a interface, there is only one method Writer:
type Writer interface { Write(p []byte) (n int, err error)}
The output function can directly invoke the parameter's write method, regardless of whether it is written to the file or written to the standard output:
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { p := newPrinter() p.doPrintf(format, a) n, err = w.Write(p.buf) //调用Write方法 p.free() return}//调用Fprintf(os.Stdout, format, a...) //标准输出Fprintf(os.Stderr, msg+"\n", args...) //标准错误输出var buf bytes.BufferFprintf(&buf, "[") //写入到Buffer的缓存中
To get to the bottom, let's look at the complete code, which is given in the custom struct field tags in Golang:
Package Mainimport ("FMT" "Reflect" "regexp" "strings") const TAGNAME = "Validate"//mailbox Verification regular var mailre = RegExp. Mustcompile (' \a[\w+\-.] +@[a-z\d\-]+ (\.[ a-z]+) *\. [a-z]+\z ')//authentication interface type Validator interface {Validate (interface{}) (bool, error)}type defaultvalidator struct {}func (v D Efaultvalidator) Validate (Val interface{}) (bool, error) {return true, Nil}type stringvalidator struct {Min int Max Int}func (v stringvalidator) Validate (Val interface{}) (bool, error) {l: = Len (String)) if L = = 0 { return False, FMT. Errorf ("cannot Be blank")} if L < V.min {return false, FMT. Errorf ("Should is at least%v chars long", v.min)} if V.max >= v.min && l > V.max {return fal SE, FMT. Errorf ("Should is less than%v chars long", V.max)} return True, Nil}type numbervalidator struct {Min int Ma X Int}func (v numbervalidator) Validate (Val interface{}) (bool, error) {num: = val. (int) If num < v.min { return False, FMT. Errorf ("should be greater than%v", v.min)} if V.max >= v.min && num > V.max {return false, F Mt. Errorf ("Should is less than%v", V.max)} return True, Nil}type emailvalidator struct {}func (v emailvalidator) Vali Date (Val interface{}) (bool, error) {if!mailre.matchstring (val. (string)) {return false, FMT. Errorf ("is not a valid email address")} return True, Nil}func Getvalidatorfromtag (tag string) Validator {args: = Strings. Split (Tag, ",") switch Args[0] {case "number": Validator: = numbervalidator{}//resolves min and Max in Structtag Into the structure of FMT. SSCANF (Strings. Join (args[1:], ","), "min=%d,max=%d", &validator. Min, &validator. MAX) Return validator Case "string": Validator: = stringvalidator{} fmt. SSCANF (Strings. Join (args[1:], ","), "min=%d,max=%d", &validator. Min, &validator. MAX) return validator case "Email": Return emailvalidator{} } return Defaultvalidator{}}func validatestruct (S interface{}) []error {errs: = []error{} V: = reflect. ValueOf (s) for i: = 0; I < V.numfield (); i++ {//Use reflection to get structtag tag: = V.type (). Field (i). Tag.get (tagName) if tag = = "" | | Tag = = "-" {continue} Validator: = Getvalidatorfromtag (tag) valid, err: = Validator. Validate (V.field (i). Interface ()) if!valid && err! = Nil {errs = append (errs, FMT. Errorf ("%s%s", V.type (). Field (i). Name, Err. Error ())}} return Errs}type User struct {Id int ' validate: "number,min=1,max=1000" ' Name stri Ng ' Validate: ' string,min=2,max=10 ' ' Bio string ' validate: ' String ' ' email string ' Validate: ' email ' '}func main () { User: = user{id:0, Name: "Superlongstring", Bio: "", Email: "Foobar", "FMT". Println ("Errors:") for I, err: = Range validatestruct (user) {fmt. Printf ("\t%d%s\n", I+1, Err. Error ())}}
The code is well understood, the structure is very clear, do not explain too much ^_^
On GitHub There is already a ready-made verification package for Govalidator, which supports built-in support for validation tags and custom validation tags:
package mainimport ( "github.com/asaskevich/govalidator" "fmt" "strings")type Server struct { ID string `valid:"uuid,required"` Name string `valid:"machine_id"` HostIP string `valid:"ip"` MacAddress string `valid:"mac,required"` WebAddress string `valid:"url"` AdminEmail string `valid:"email"`}func main() { server := &Server{ ID: "123e4567-e89b-12d3-a456-426655440000", Name: "IX01", HostIP: "127.0.0.1", MacAddress: "01:23:45:67:89:ab", WebAddress: "www.example.com", AdminEmail: "admin@exmaple.com", } //自定义tag验证函数 govalidator.TagMap["machine_id"] = govalidator.Validator(func(str string) bool { return strings.HasPrefix(str, "IX") }) if ok, err := govalidator.ValidateStruct(server); err != nil { panic(err) } else { fmt.Printf("OK: %v\n", ok) }}
Resources:
- Custom struct field tags in golang
- Data validation in Golang
- govalidator