golang 驗證struct欄位的資料格式
阿新 • • 發佈:2019-01-03
假設我們有如下結構體:
type User struct {
Id int
Name string
Bio string
Email string
}
我們需要對結構體內的欄位進行驗證合法性:
·Id的值在某一個範圍內。
·Name的長度在莫一個範圍內。
·Email格式正確。
我們可能會這麼寫:
這樣的話程式碼比較冗餘,而且如果結構體新加欄位,還需要再修改驗證函式再加一段if判斷。這樣程式碼比較冗餘。我們可以藉助golang的structTag來解決上述的問題: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 }
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"就是structTag。實現思路
我們定義一個介面Validator,定義一個方法Validate。再定義有具體意義的驗證器例如StringValidator、NumberValidator、EmailValidator來實現介面Validator。
完整例項
其實github上已經有現成的驗證包了govalidator,支援內建的驗證tag和自定義驗證tag:package main import ( "fmt" "reflect" "regexp" "strings" ) const tagName = "validate" //郵箱驗證正則 var mailRe = regexp.MustCompile(`\A[\w+\-.][email protected][a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z`) //驗證介面 type Validator interface { Validate(interface{}) (bool, error) } type DefaultValidator struct { } func (v DefaultValidator) 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(val.(string)) if l == 0 { return false, fmt.Errorf("cannot be blank") } if l < v.Min { return false, fmt.Errorf("should be at least %v chars long", v.Min) } if v.Max >= v.Min && l > v.Max { return false, fmt.Errorf("should be less than %v chars long", v.Max) } return true, nil } type NumberValidator struct { Min int Max 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, fmt.Errorf("should be less than %v", v.Max) } return true, nil } type EmailValidator struct { } func (v EmailValidator) Validate(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{} //將structTag中的min和max解析到結構體中 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++ { //利用反射獲取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 string `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()) } }
package main
import (
"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: "[email protected]",
}
//自定義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)
}
}