From c30d72889598ddc27b6488f557fd256fd7f01f78 Mon Sep 17 00:00:00 2001 From: henrylee2cn Date: Mon, 22 Jul 2019 23:38:28 +0800 Subject: [PATCH] feat: The method *TagExpr.Rang supports traversing the values of interfaces in the map, slice, and array Change-Id: I5721d03114d080fed2ce34186365f4be15878a73 --- tagexpr.go | 131 +++++++++++++++++++++++++++++++++++++---- tagexpr_test.go | 35 +++++++---- validator/validator.go | 11 ++-- 3 files changed, 146 insertions(+), 31 deletions(-) diff --git a/tagexpr.go b/tagexpr.go index 0a4ebb7..c68d60c 100644 --- a/tagexpr.go +++ b/tagexpr.go @@ -23,6 +23,7 @@ import ( "sync" "unsafe" + "github.com/henrylee2cn/goutil" "github.com/henrylee2cn/goutil/tpack" ) @@ -58,6 +59,7 @@ type fieldVM struct { origin *structVM mapKeyStructVM *structVM mapOrSliceElemStructVM *structVM + mapOrSliceIfaceKinds [2]bool // [value, key/index] tagOp string } @@ -220,7 +222,12 @@ func (vm *VM) registerIndirectStructLocked(field *fieldVM) error { a = append(a, derefType(field.elemType.Key())) } for i, t := range a { - if t.Kind() != reflect.Struct { + kind := t.Kind() + if kind != reflect.Struct { + if kind == reflect.Interface { + field.mapOrSliceIfaceKinds[i] = true + field.origin.fieldsWithIndirectStructVM = append(field.origin.fieldsWithIndirectStructVM, field) + } continue } s, err := vm.registerStructLocked(t) @@ -660,47 +667,143 @@ func (t *TagExpr) Range(fn func(es ExprSelector, eval func() interface{}) bool) for _, f := range list { v := f.packElemFrom(t.ptr) omitNil := f.tagOp == tagOmitNil + mapKeyStructVM := f.mapKeyStructVM + mapOrSliceElemStructVM := f.mapOrSliceElemStructVM + valueIface := f.mapOrSliceIfaceKinds[0] + keyIface := f.mapOrSliceIfaceKinds[1] - if f.elemKind == reflect.Map { + if f.elemKind == reflect.Map && + (mapOrSliceElemStructVM != nil || mapKeyStructVM != nil || valueIface || keyIface) { for _, key := range v.MapKeys() { - if f.mapKeyStructVM != nil { + if mapKeyStructVM != nil { ptr := tpack.From(derefValue(key)).Pointer() if omitNil && ptr == 0 { continue } - if !f.mapKeyStructVM.newTagExpr(ptr).Range(fn) { + if !mapKeyStructVM.newTagExpr(ptr).Range(fn) { return false } + } else if keyIface && !t.subRange(omitNil, key, fn) { + return false } - if f.mapOrSliceElemStructVM != nil { + if mapOrSliceElemStructVM != nil { ptr := tpack.From(derefValue(v.MapIndex(key))).Pointer() if omitNil && ptr == 0 { continue } - if !f.mapOrSliceElemStructVM.newTagExpr(ptr).Range(fn) { + if !mapOrSliceElemStructVM.newTagExpr(ptr).Range(fn) { return false } + } else if valueIface && !t.subRange(omitNil, v.MapIndex(key), fn) { + return false } } - } else { + } else if mapOrSliceElemStructVM != nil || valueIface { // slice or array for i := v.Len() - 1; i >= 0; i-- { - ptr := tpack.From(derefValue(v.Index(i))).Pointer() - if omitNil && ptr == 0 { - continue - } - if !f.mapOrSliceElemStructVM.newTagExpr(ptr).Range(fn) { + if mapOrSliceElemStructVM != nil { + ptr := tpack.From(derefValue(v.Index(i))).Pointer() + if omitNil && ptr == 0 { + continue + } + if !mapOrSliceElemStructVM.newTagExpr(ptr).Range(fn) { + return false + } + } else if valueIface && !t.subRange(omitNil, v.Index(i), fn) { return false } } } } } + return true +} +func (t *TagExpr) subRange(omitNil bool, value reflect.Value, fn func(es ExprSelector, eval func() interface{}) bool) bool { + rv := goutil.DereferenceIfaceValue(value) + rt := goutil.DereferenceType(rv.Type()) + rv = goutil.DereferenceValue(rv) + switch rt.Kind() { + case reflect.Struct: + if omitNil && !rv.IsValid() { + return true + } + te, err := t.s.vm.subRun(rt, tpack.RuntimeTypeID(rt), tpack.From(rv).Pointer()) + if err != nil { + return false + } + return te.Range(fn) + + case reflect.Slice, reflect.Array: + count := rv.Len() + if count == 0 { + return true + } + switch goutil.DereferenceType(rv.Type().Elem()).Kind() { + case reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map: + for i := count - 1; i >= 0; i-- { + if !t.subRange(omitNil, rv.Index(i), fn) { + return false + } + } + default: + return true + } + + case reflect.Map: + if rv.Len() == 0 { + return true + } + var canKey, canValue bool + rt := rv.Type() + switch goutil.DereferenceType(rt.Key()).Kind() { + case reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map: + canKey = true + } + switch goutil.DereferenceType(rt.Elem()).Kind() { + case reflect.Struct, reflect.Interface, reflect.Slice, reflect.Array, reflect.Map: + canValue = true + } + if !canKey && !canValue { + return true + } + for _, key := range rv.MapKeys() { + if canKey { + if !t.subRange(omitNil, key, fn) { + return false + } + } + if canValue { + if !t.subRange(omitNil, rv.MapIndex(key), fn) { + return false + } + } + } + } return true } +func (vm *VM) subRun(t reflect.Type, tid int32, ptr uintptr) (*TagExpr, error) { + var err error + vm.rw.RLock() + s, ok := vm.structJar[tid] + vm.rw.RUnlock() + if !ok { + vm.rw.Lock() + s, ok = vm.structJar[tid] + if !ok { + s, err = vm.registerStructLocked(t) + if err != nil { + vm.rw.Unlock() + return nil, err + } + } + vm.rw.Unlock() + } + return s.newTagExpr(ptr), nil +} + var ( errFieldSelector = errors.New("field selector does not exist") errOmitNil = errors.New("omit nil") @@ -712,6 +815,9 @@ func (t *TagExpr) checkout(fs string) (*TagExpr, error) { } subTagExpr, ok := t.sub[fs] if ok { + if subTagExpr == nil { + return nil, errOmitNil + } return subTagExpr, nil } f, ok := t.s.fields[fs] @@ -720,6 +826,7 @@ func (t *TagExpr) checkout(fs string) (*TagExpr, error) { } ptr := f.elemPtr(t.ptr) if f.tagOp == tagOmitNil && unsafe.Pointer(ptr) == nil { + t.sub[fs] = nil return nil, errOmitNil } subTagExpr = f.origin.newTagExpr(ptr) diff --git a/tagexpr_test.go b/tagexpr_test.go index a3d1fad..4416f0e 100644 --- a/tagexpr_test.go +++ b/tagexpr_test.go @@ -714,19 +714,28 @@ func TestNilField(t *testing.T) { return true }) - type N struct { - X string `tagexpr:"len($)>0"` - N *N `tagexpr:"?"` - S []*N `tagexpr:"?"` - M map[string]*N `tagexpr:"?"` - I interface{} `tagexpr:"?"` - } + type ( + N struct { + X string `tagexpr:"len($)>0"` + N *N `tagexpr:"?"` + S []*N `tagexpr:"?"` + M map[string]*N `tagexpr:"?"` + I interface{} `tagexpr:"-"` + MI map[string]interface{} `tagexpr:"?"` + SI []interface{} + } + M struct { + X string `tagexpr:"len($)>0"` + } + ) n := &N{ - X: "n", - N: nil, - S: []*N{nil}, - M: map[string]*N{"": nil}, - I: nil, + X: "n", + N: nil, + S: []*N{nil}, + M: map[string]*N{"": nil}, + I: new(N), + MI: map[string]interface{}{"": (*M)(nil)}, + SI: []interface{}{&M{X: "nn"}}, } var cnt int vm.MustRun(n).Range(func(es ExprSelector, eval func() interface{}) bool { @@ -738,7 +747,7 @@ func TestNilField(t *testing.T) { cnt++ return true }) - if cnt != 1 { + if cnt != 2 { t.FailNow() } } diff --git a/validator/validator.go b/validator/validator.go index 16f8eae..ef22c5a 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -63,8 +63,10 @@ func (v *Validator) Validate(value interface{}) error { } func (v *Validator) validate(selectorPrefix string, value reflect.Value) error { - rv := goutil.DereferenceValue(value) - switch rv.Kind() { + rv := goutil.DereferenceIfaceValue(value) + rt := goutil.DereferenceType(rv.Type()) + rv = goutil.DereferenceValue(rv) + switch rt.Kind() { case reflect.Struct: break @@ -113,10 +115,7 @@ func (v *Validator) validate(selectorPrefix string, value reflect.Value) error { } } } - default: - if goutil.DereferenceType(value.Type()).Kind() != reflect.Struct { - return nil - } + return nil } expr, err := v.vm.Run(rv)