package main
import (
"fmt"
"reflect"
"strconv"
"strings"
)
type Inner struct {
Email string `validate:"email"`
}
type Outer struct {
Age int `validate:"eq=15"`
Inner Inner
}
func validateEmail(email string) bool {
return true
}
func validate(v interface{}) (bool, string) {
validateRes := true
errMsg := "success"
vt := reflect.TypeOf(v)
vv := reflect.ValueOf(v)
// fmt.Println("vv:", vv)
// fmt.Println("vv's kind :", vv.Kind())
for i := 0; i < vv.NumField(); i++ {
fieldVal := vv.Field(i)
k := vv.Kind()
validatestr := vt.Field(i).Tag.Get("validate")
switch k {
case reflect.Int:
val := fieldVal.Int()
// fmt.Println("int...")
tagVal := strings.Split(validatestr, "=")
tagValInt, _ := strconv.ParseInt(tagVal[1], 10, 64)
if val != tagValInt {
errMsg = "validate int failed,tag is " + strconv.FormatInt(tagValInt, 10)
validateRes = false
}
case reflect.String:
val := fieldVal.String()
tagStr := validatestr
switch tagStr {
case "email":
emailValidate := validateEmail(val)
if !emailValidate {
errMsg = "validate email failed ,field val is : " + val
validateRes = false
}
}
case reflect.Struct:
// fmt.Println("access struct")
valInter := fieldVal.Interface()
innerRes, err := validate(valInter)
if !innerRes {
validateRes = innerRes
errMsg = err
}
}
}
return validateRes, errMsg
}
func main() {
inner := Inner{
Email: "[email protected]",
}
outer := Outer{
Age: 15,
Inner: inner,
}
res, err := validate(outer)
if !res {
fmt.Println(err)
}
}
这是一个校验器的实现,利用的是树的深度遍历算法,以及反射的知识。然而运行后报错如下:
panic: reflect: call of reflect.Value.NumField on int Value
goroutine 1 [running]:
reflect.flag.mustBe(0x82, 0x19)
E:/software/develop/go/src/reflect/value.go:207 +0xbd
reflect.Value.NumField(...)
E:/software/develop/go/src/reflect/value.go:1292
main.validate(0x4a6800, 0xc000004480, 0x82, 0x1, 0x4a6800)
F:/go-code/src/go_advanced_pro/validate/demo01/main.go:33 +0x165
main.validate(0x4b35a0, 0xc000004480, 0x4b35a0, 0xc000004480, 0x493abb)
F:/go-code/src/go_advanced_pro/validate/demo01/main.go:63 +0x2ee
main.main()
F:/go-code/src/go_advanced_pro/validate/demo01/main.go:82 +0x78
exit status 2
根据报错信息可以追踪到程序第33行出错了
for i := 0; i < vv.NumField(); i++ {...
这一行出错的唯一可能就是vv.NumField()出错了。跟踪错误:ctrl+左进入mustBe()方法:
// mustBe panics if f's kind is not expected.
// Making this a method on flag instead of on Value
// (and embedding flag in Value) means that we can write
// the very clear v.mustBe(Bool) and have it compile into
// v.flag.mustBe(Bool), which will only bother to copy the
// single important word for the receiver.
func (f flag) mustBe(expected Kind) {
if f.kind() != expected {
panic(&ValueError{methodName(), f.kind()})
}
}
看注释知道这是个要接收期待的kind才能成功运行的方法,于是就去看看传入这个方法的kind是什么,ctri+左进入NumField()方法。
// NumField returns the number of fields in the struct v.
// It panics if v's Kind is not Struct.
func (v Value) NumField() int {
v.mustBe(Struct)
tt := (*structType)(unsafe.Pointer(v.typ))
return len(tt.fields)
}
可以看到要求传入的必须是结构体。
回过头来检查我们的代码,main方法里传入的是个结构体,但是报这个错表明递归的时候传入出错了。加上下面这句代码重新执行
fmt.Println("vv's kind :", vv.Kind()) //去掉上面代码的注释
结果是
vv's kind : struct
vv's kind : int
可以看到递归进去后变成int了,检查代码后发现用来进行switch匹配的k 由这段代码得到
k := vv.Kind()
恍然大悟,这里不应该是vv来调用,而是应该fieldVal来调用,因为我们是根据传入结构体属性的值是什么类型来决定走哪个分支,而不是根据结构体的类型。。
粗心使人进步 = =