mirror of
https://github.com/Fishwaldo/validator.git
synced 2025-03-16 04:01:32 +00:00
Merge pull request #87 from bluesuncorp/v5-development
Correct interface panic & add interface type handling
This commit is contained in:
commit
d627af88ac
2 changed files with 344 additions and 16 deletions
140
validator.go
140
validator.go
|
@ -353,7 +353,6 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
|
|||
structName = structType.Name()
|
||||
numFields = structValue.NumField()
|
||||
cs = &cachedStruct{name: structName, children: numFields}
|
||||
structCache.Set(structType, cs)
|
||||
}
|
||||
|
||||
validationErrors := structPool.Borrow()
|
||||
|
@ -429,24 +428,62 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
|
|||
continue
|
||||
}
|
||||
|
||||
if valueField.Kind() == reflect.Ptr && valueField.IsNil() {
|
||||
if (valueField.Kind() == reflect.Ptr || cField.kind == reflect.Interface) && valueField.IsNil() {
|
||||
|
||||
if strings.Contains(cField.tag, omitempty) {
|
||||
continue
|
||||
goto CACHEFIELD
|
||||
}
|
||||
|
||||
if strings.Contains(cField.tag, required) {
|
||||
tags := strings.Split(cField.tag, tagSeparator)
|
||||
|
||||
if len(tags) > 0 {
|
||||
|
||||
var param string
|
||||
vals := strings.SplitN(tags[0], tagKeySeparator, 2)
|
||||
|
||||
if len(vals) > 1 {
|
||||
param = vals[1]
|
||||
}
|
||||
|
||||
validationErrors.Errors[cField.name] = &FieldError{
|
||||
Field: cField.name,
|
||||
Tag: required,
|
||||
Tag: vals[0],
|
||||
Param: param,
|
||||
Value: valueField.Interface(),
|
||||
Kind: valueField.Kind(),
|
||||
Type: valueField.Type(),
|
||||
}
|
||||
|
||||
continue
|
||||
goto CACHEFIELD
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here, the field is interface and could be a struct or a field
|
||||
// and we need to check the inner type and validate
|
||||
if cField.kind == reflect.Interface {
|
||||
|
||||
valueField = valueField.Elem()
|
||||
|
||||
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
|
||||
valueField = valueField.Elem()
|
||||
}
|
||||
|
||||
if valueField.Kind() == reflect.Struct {
|
||||
goto VALIDATESTRUCT
|
||||
}
|
||||
|
||||
// sending nil for cField as it was type interface and could be anything
|
||||
// each time and so must be calculated each time and can't be cached reliably
|
||||
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, nil); fieldError != nil {
|
||||
validationErrors.Errors[fieldError.Field] = fieldError
|
||||
// free up memory reference
|
||||
fieldError = nil
|
||||
}
|
||||
|
||||
goto CACHEFIELD
|
||||
}
|
||||
|
||||
VALIDATESTRUCT:
|
||||
if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil {
|
||||
validationErrors.StructErrors[cField.name] = structErrors
|
||||
// free up memory map no longer needed
|
||||
|
@ -486,11 +523,14 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
|
|||
}
|
||||
}
|
||||
|
||||
CACHEFIELD:
|
||||
if !isCached {
|
||||
cs.fields = append(cs.fields, cField)
|
||||
}
|
||||
}
|
||||
|
||||
structCache.Set(structType, cs)
|
||||
|
||||
if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 {
|
||||
structPool.Return(validationErrors)
|
||||
return nil
|
||||
|
@ -709,19 +749,29 @@ func (v *Validate) traverseMap(val interface{}, current interface{}, valueField
|
|||
continue
|
||||
}
|
||||
|
||||
if idxField.Kind() == reflect.Ptr && idxField.IsNil() {
|
||||
if (idxField.Kind() == reflect.Ptr || idxField.Kind() == reflect.Interface) && idxField.IsNil() {
|
||||
|
||||
if strings.Contains(cField.tag, omitempty) {
|
||||
if strings.Contains(cField.diveTag, omitempty) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(cField.tag, required) {
|
||||
tags := strings.Split(cField.diveTag, tagSeparator)
|
||||
|
||||
if len(tags) > 0 {
|
||||
|
||||
var param string
|
||||
vals := strings.SplitN(tags[0], tagKeySeparator, 2)
|
||||
|
||||
if len(vals) > 1 {
|
||||
param = vals[1]
|
||||
}
|
||||
|
||||
errs[key.Interface()] = &FieldError{
|
||||
Field: fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()),
|
||||
Tag: required,
|
||||
Tag: vals[0],
|
||||
Param: param,
|
||||
Value: idxField.Interface(),
|
||||
Kind: reflect.Ptr,
|
||||
Kind: idxField.Kind(),
|
||||
Type: cField.mapSubtype,
|
||||
}
|
||||
}
|
||||
|
@ -729,6 +779,30 @@ func (v *Validate) traverseMap(val interface{}, current interface{}, valueField
|
|||
continue
|
||||
}
|
||||
|
||||
// if we get here, the field is interface and could be a struct or a field
|
||||
// and we need to check the inner type and validate
|
||||
if idxField.Kind() == reflect.Interface {
|
||||
|
||||
idxField = idxField.Elem()
|
||||
|
||||
if idxField.Kind() == reflect.Ptr && !idxField.IsNil() {
|
||||
idxField = idxField.Elem()
|
||||
}
|
||||
|
||||
if idxField.Kind() == reflect.Struct {
|
||||
goto VALIDATESTRUCT
|
||||
}
|
||||
|
||||
// sending nil for cField as it was type interface and could be anything
|
||||
// each time and so must be calculated each time and can't be cached reliably
|
||||
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(mapIndexFieldName, cField.name, key.Interface()), false, nil); fieldError != nil {
|
||||
errs[key.Interface()] = fieldError
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
VALIDATESTRUCT:
|
||||
if structErrors := v.structRecursive(val, current, idxField.Interface()); structErrors != nil {
|
||||
errs[key.Interface()] = structErrors
|
||||
}
|
||||
|
@ -768,19 +842,29 @@ func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, va
|
|||
continue
|
||||
}
|
||||
|
||||
if idxField.Kind() == reflect.Ptr && idxField.IsNil() {
|
||||
if (idxField.Kind() == reflect.Ptr || idxField.Kind() == reflect.Interface) && idxField.IsNil() {
|
||||
|
||||
if strings.Contains(cField.tag, omitempty) {
|
||||
if strings.Contains(cField.diveTag, omitempty) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(cField.tag, required) {
|
||||
tags := strings.Split(cField.diveTag, tagSeparator)
|
||||
|
||||
if len(tags) > 0 {
|
||||
|
||||
var param string
|
||||
vals := strings.SplitN(tags[0], tagKeySeparator, 2)
|
||||
|
||||
if len(vals) > 1 {
|
||||
param = vals[1]
|
||||
}
|
||||
|
||||
errs[i] = &FieldError{
|
||||
Field: fmt.Sprintf(arrayIndexFieldName, cField.name, i),
|
||||
Tag: required,
|
||||
Tag: vals[0],
|
||||
Param: param,
|
||||
Value: idxField.Interface(),
|
||||
Kind: reflect.Ptr,
|
||||
Kind: idxField.Kind(),
|
||||
Type: cField.sliceSubtype,
|
||||
}
|
||||
}
|
||||
|
@ -788,6 +872,30 @@ func (v *Validate) traverseSliceOrArray(val interface{}, current interface{}, va
|
|||
continue
|
||||
}
|
||||
|
||||
// if we get here, the field is interface and could be a struct or a field
|
||||
// and we need to check the inner type and validate
|
||||
if idxField.Kind() == reflect.Interface {
|
||||
|
||||
idxField = idxField.Elem()
|
||||
|
||||
if idxField.Kind() == reflect.Ptr && !idxField.IsNil() {
|
||||
idxField = idxField.Elem()
|
||||
}
|
||||
|
||||
if idxField.Kind() == reflect.Struct {
|
||||
goto VALIDATESTRUCT
|
||||
}
|
||||
|
||||
// sending nil for cField as it was type interface and could be anything
|
||||
// each time and so must be calculated each time and can't be cached reliably
|
||||
if fieldError := v.fieldWithNameAndValue(val, current, idxField.Interface(), cField.diveTag, fmt.Sprintf(arrayIndexFieldName, cField.name, i), false, nil); fieldError != nil {
|
||||
errs[i] = fieldError
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
VALIDATESTRUCT:
|
||||
if structErrors := v.structRecursive(val, current, idxField.Interface()); structErrors != nil {
|
||||
errs[i] = structErrors
|
||||
}
|
||||
|
|
|
@ -226,6 +226,226 @@ func AssertMapFieldError(t *testing.T, s map[string]*FieldError, field string, e
|
|||
EqualSkip(t, 2, val.Tag, expectedTag)
|
||||
}
|
||||
|
||||
func TestInterfaceErrValidation(t *testing.T) {
|
||||
|
||||
var v1 interface{}
|
||||
var v2 interface{}
|
||||
|
||||
v2 = 1
|
||||
v1 = v2
|
||||
|
||||
err := validate.Field(v1, "len=1")
|
||||
Equal(t, err, nil)
|
||||
err = validate.Field(v2, "len=1")
|
||||
Equal(t, err, nil)
|
||||
|
||||
type ExternalCMD struct {
|
||||
Userid string `json:"userid"`
|
||||
Action uint32 `json:"action"`
|
||||
Data interface{} `json:"data,omitempty" validate:"required"`
|
||||
}
|
||||
|
||||
s := &ExternalCMD{
|
||||
Userid: "123456",
|
||||
Action: 10000,
|
||||
// Data: 1,
|
||||
}
|
||||
|
||||
errs := validate.Struct(s)
|
||||
NotEqual(t, errs, nil)
|
||||
Equal(t, errs.Errors["Data"].Field, "Data")
|
||||
Equal(t, errs.Errors["Data"].Tag, "required")
|
||||
|
||||
type ExternalCMD2 struct {
|
||||
Userid string `json:"userid"`
|
||||
Action uint32 `json:"action"`
|
||||
Data interface{} `json:"data,omitempty" validate:"len=1"`
|
||||
}
|
||||
|
||||
s2 := &ExternalCMD2{
|
||||
Userid: "123456",
|
||||
Action: 10000,
|
||||
// Data: 1,
|
||||
}
|
||||
|
||||
errs = validate.Struct(s2)
|
||||
NotEqual(t, errs, nil)
|
||||
Equal(t, errs.Errors["Data"].Field, "Data")
|
||||
Equal(t, errs.Errors["Data"].Tag, "len")
|
||||
Equal(t, errs.Errors["Data"].Param, "1")
|
||||
|
||||
s3 := &ExternalCMD2{
|
||||
Userid: "123456",
|
||||
Action: 10000,
|
||||
Data: 2,
|
||||
}
|
||||
|
||||
errs = validate.Struct(s3)
|
||||
NotEqual(t, errs, nil)
|
||||
Equal(t, errs.Errors["Data"].Field, "Data")
|
||||
Equal(t, errs.Errors["Data"].Tag, "len")
|
||||
Equal(t, errs.Errors["Data"].Param, "1")
|
||||
|
||||
type Inner struct {
|
||||
Name string `validate:"required"`
|
||||
}
|
||||
|
||||
inner := &Inner{
|
||||
Name: "",
|
||||
}
|
||||
|
||||
s4 := &ExternalCMD{
|
||||
Userid: "123456",
|
||||
Action: 10000,
|
||||
Data: inner,
|
||||
}
|
||||
|
||||
errs = validate.Struct(s4)
|
||||
NotEqual(t, errs, nil)
|
||||
Equal(t, errs.StructErrors["Data"].Struct, "Inner")
|
||||
Equal(t, errs.StructErrors["Data"].Errors["Name"].Field, "Name")
|
||||
Equal(t, errs.StructErrors["Data"].Errors["Name"].Tag, "required")
|
||||
|
||||
type TestMapStructPtr struct {
|
||||
Errs map[int]interface{} `validate:"gt=0,dive,len=2"`
|
||||
}
|
||||
|
||||
mip := map[int]interface{}{0: &Inner{"ok"}, 3: nil, 4: &Inner{"ok"}}
|
||||
|
||||
msp := &TestMapStructPtr{
|
||||
Errs: mip,
|
||||
}
|
||||
|
||||
errs = validate.Struct(msp)
|
||||
NotEqual(t, errs, nil)
|
||||
Equal(t, len(errs.Errors), 1)
|
||||
|
||||
fieldError := errs.Errors["Errs"]
|
||||
Equal(t, fieldError.IsPlaceholderErr, true)
|
||||
Equal(t, fieldError.IsMap, true)
|
||||
Equal(t, len(fieldError.MapErrs), 1)
|
||||
|
||||
innerFieldError, ok := fieldError.MapErrs[3].(*FieldError)
|
||||
Equal(t, ok, true)
|
||||
Equal(t, innerFieldError.IsPlaceholderErr, false)
|
||||
Equal(t, innerFieldError.IsMap, false)
|
||||
Equal(t, len(innerFieldError.MapErrs), 0)
|
||||
Equal(t, innerFieldError.Field, "Errs[3]")
|
||||
Equal(t, innerFieldError.Tag, "len")
|
||||
|
||||
type TestMultiDimensionalStructs struct {
|
||||
Errs [][]interface{} `validate:"gt=0,dive,dive,len=2"`
|
||||
}
|
||||
|
||||
var errStructArray [][]interface{}
|
||||
|
||||
errStructArray = append(errStructArray, []interface{}{&Inner{"ok"}, &Inner{""}, &Inner{""}})
|
||||
errStructArray = append(errStructArray, []interface{}{&Inner{"ok"}, &Inner{""}, &Inner{""}})
|
||||
|
||||
tms := &TestMultiDimensionalStructs{
|
||||
Errs: errStructArray,
|
||||
}
|
||||
|
||||
errs = validate.Struct(tms)
|
||||
NotEqual(t, errs, nil)
|
||||
Equal(t, len(errs.Errors), 1)
|
||||
|
||||
fieldErr, ok := errs.Errors["Errs"]
|
||||
Equal(t, ok, true)
|
||||
Equal(t, fieldErr.IsPlaceholderErr, true)
|
||||
Equal(t, fieldErr.IsSliceOrArray, true)
|
||||
Equal(t, len(fieldErr.SliceOrArrayErrs), 2)
|
||||
|
||||
sliceError1, ok := fieldErr.SliceOrArrayErrs[0].(*FieldError)
|
||||
Equal(t, ok, true)
|
||||
Equal(t, sliceError1.IsPlaceholderErr, true)
|
||||
Equal(t, sliceError1.IsSliceOrArray, true)
|
||||
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
|
||||
|
||||
innerSliceStructError1, ok := sliceError1.SliceOrArrayErrs[1].(*StructErrors)
|
||||
Equal(t, ok, true)
|
||||
Equal(t, len(innerSliceStructError1.Errors), 1)
|
||||
|
||||
innerInnersliceError1 := innerSliceStructError1.Errors["Name"]
|
||||
Equal(t, innerInnersliceError1.IsPlaceholderErr, false)
|
||||
Equal(t, innerInnersliceError1.IsSliceOrArray, false)
|
||||
Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0)
|
||||
|
||||
type TestMultiDimensionalStructsPtr2 struct {
|
||||
Errs [][]*Inner `validate:"gt=0,dive,dive,len=2"`
|
||||
}
|
||||
|
||||
var errStructPtr2Array [][]*Inner
|
||||
|
||||
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}})
|
||||
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, &Inner{""}})
|
||||
errStructPtr2Array = append(errStructPtr2Array, []*Inner{&Inner{"ok"}, &Inner{""}, nil})
|
||||
|
||||
tmsp2 := &TestMultiDimensionalStructsPtr2{
|
||||
Errs: errStructPtr2Array,
|
||||
}
|
||||
|
||||
errs = validate.Struct(tmsp2)
|
||||
NotEqual(t, errs, nil)
|
||||
Equal(t, len(errs.Errors), 1)
|
||||
|
||||
fieldErr, ok = errs.Errors["Errs"]
|
||||
Equal(t, ok, true)
|
||||
Equal(t, fieldErr.IsPlaceholderErr, true)
|
||||
Equal(t, fieldErr.IsSliceOrArray, true)
|
||||
Equal(t, len(fieldErr.SliceOrArrayErrs), 3)
|
||||
|
||||
sliceError1, ok = fieldErr.SliceOrArrayErrs[2].(*FieldError)
|
||||
Equal(t, ok, true)
|
||||
Equal(t, sliceError1.IsPlaceholderErr, true)
|
||||
Equal(t, sliceError1.IsSliceOrArray, true)
|
||||
Equal(t, len(sliceError1.SliceOrArrayErrs), 2)
|
||||
|
||||
innerSliceStructError1, ok = sliceError1.SliceOrArrayErrs[1].(*StructErrors)
|
||||
Equal(t, ok, true)
|
||||
Equal(t, len(innerSliceStructError1.Errors), 1)
|
||||
|
||||
innerSliceStructError2, ok := sliceError1.SliceOrArrayErrs[2].(*FieldError)
|
||||
Equal(t, ok, true)
|
||||
Equal(t, innerSliceStructError2.IsPlaceholderErr, false)
|
||||
Equal(t, innerSliceStructError2.IsSliceOrArray, false)
|
||||
Equal(t, len(innerSliceStructError2.SliceOrArrayErrs), 0)
|
||||
Equal(t, innerSliceStructError2.Field, "Errs[2][2]")
|
||||
|
||||
innerInnersliceError1 = innerSliceStructError1.Errors["Name"]
|
||||
Equal(t, innerInnersliceError1.IsPlaceholderErr, false)
|
||||
Equal(t, innerInnersliceError1.IsSliceOrArray, false)
|
||||
Equal(t, len(innerInnersliceError1.SliceOrArrayErrs), 0)
|
||||
|
||||
m := map[int]interface{}{0: "ok", 3: "", 4: "ok"}
|
||||
|
||||
err = validate.Field(m, "len=3,dive,len=2")
|
||||
NotEqual(t, err, nil)
|
||||
Equal(t, err.IsPlaceholderErr, true)
|
||||
Equal(t, err.IsMap, true)
|
||||
Equal(t, len(err.MapErrs), 1)
|
||||
|
||||
err = validate.Field(m, "len=2,dive,required")
|
||||
NotEqual(t, err, nil)
|
||||
Equal(t, err.IsPlaceholderErr, false)
|
||||
Equal(t, err.IsMap, false)
|
||||
Equal(t, len(err.MapErrs), 0)
|
||||
|
||||
arr := []interface{}{"ok", "", "ok"}
|
||||
|
||||
err = validate.Field(arr, "len=3,dive,len=2")
|
||||
NotEqual(t, err, nil)
|
||||
Equal(t, err.IsPlaceholderErr, true)
|
||||
Equal(t, err.IsSliceOrArray, true)
|
||||
Equal(t, len(err.SliceOrArrayErrs), 1)
|
||||
|
||||
err = validate.Field(arr, "len=2,dive,required")
|
||||
NotEqual(t, err, nil)
|
||||
Equal(t, err.IsPlaceholderErr, false)
|
||||
Equal(t, err.IsSliceOrArray, false)
|
||||
Equal(t, len(err.SliceOrArrayErrs), 0)
|
||||
}
|
||||
|
||||
func TestMapDiveValidation(t *testing.T) {
|
||||
|
||||
m := map[int]string{0: "ok", 3: "", 4: "ok"}
|
||||
|
|
Loading…
Add table
Reference in a new issue