diff --git a/util.go b/util.go index 4dd39ba..1494690 100644 --- a/util.go +++ b/util.go @@ -6,7 +6,13 @@ import ( "strings" ) -func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect.Kind) { +const ( + namespaceSeparator = "." + leftBracket = "[" + rightBracket = "]" +) + +func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Kind) { switch current.Kind() { case reflect.Ptr: @@ -15,7 +21,7 @@ func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect. return current, reflect.Ptr } - return v.determineType(current.Elem()) + return v.extractType(current.Elem()) case reflect.Interface: @@ -23,7 +29,7 @@ func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect. return current, reflect.Interface } - return v.determineType(current.Elem()) + return v.extractType(current.Elem()) case reflect.Invalid: @@ -31,10 +37,9 @@ func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect. default: - // fmt.Println(current.Kind()) if v.config.hasCustomFuncs { if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok { - return v.determineType(reflect.ValueOf(fn(current))) + return v.extractType(reflect.ValueOf(fn(current))) } } @@ -44,14 +49,15 @@ func (v *Validate) determineType(current reflect.Value) (reflect.Value, reflect. func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) { - // fmt.Println("NS:", namespace) - - current, kind := v.determineType(current) - - // fmt.Println("getStructFieldOK - ", current, kind) + current, kind := v.extractType(current) switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: + + if len(namespace) == 0 { + return current, kind, true + } + return current, kind, false case reflect.Struct: @@ -63,50 +69,34 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re idx := strings.Index(namespace, namespaceSeparator) - // fmt.Println("IDX:", namespace, idx) if idx != -1 { fld = namespace[:idx] } ns := namespace[idx+1:] - bracketIdx := strings.Index(fld, "[") + bracketIdx := strings.Index(fld, leftBracket) if bracketIdx != -1 { fld = fld[:bracketIdx] - // fmt.Println("NSS:", ns) if idx == -1 { ns = namespace[bracketIdx:] } else { ns = namespace[bracketIdx:] } - // fmt.Println("NSS2:", ns) } - // fmt.Println("Looking for field:", fld) current = current.FieldByName(fld) - // fmt.Println("Current:", current) - return v.getStructFieldOK(current, ns) } case reflect.Array, reflect.Slice: - idx := strings.Index(namespace, "[") - idx2 := strings.Index(namespace, "]") - // idx3 := strings.Index(namespace, namespaceSeparator) - - // if idx3 == -1 { - // idx3 = 0 - // } else { - // idx3 = 1 - // } - // + idx := strings.Index(namespace, leftBracket) + idx2 := strings.Index(namespace, rightBracket) arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2]) - // fmt.Println("ArrayIndex:", arrIdx) - // fmt.Println("LEN:", current.Len()) if arrIdx >= current.Len() { return current, kind, false } @@ -114,7 +104,7 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re startIdx := idx2 + 1 if startIdx < len(namespace) { - if namespace[startIdx:startIdx+1] == "." { + if namespace[startIdx:startIdx+1] == namespaceSeparator { startIdx++ } } @@ -122,27 +112,19 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re return v.getStructFieldOK(current.Index(arrIdx), namespace[startIdx:]) case reflect.Map: - idx := strings.Index(namespace, "[") + 1 - idx2 := strings.Index(namespace, "]") + idx := strings.Index(namespace, leftBracket) + 1 + idx2 := strings.Index(namespace, rightBracket) endIdx := idx2 - // fmt.Println("END IDX:", endIdx) - // fmt.Println("L NS:", len(namespace)) - // fmt.Println("NS:", namespace) - if endIdx+1 < len(namespace) { - if namespace[endIdx+1:endIdx+2] == "." { + if namespace[endIdx+1:endIdx+2] == namespaceSeparator { endIdx++ } } - // fmt.Println("KEY:", namespace[idx:idx2]) - // fmt.Println("KEY NS:", namespace[endIdx+1:]) - return v.getStructFieldOK(current.MapIndex(reflect.ValueOf(namespace[idx:idx2])), namespace[endIdx+1:]) } - // fmt.Println("Returning field") return current, kind, true } diff --git a/validator.go b/validator.go index cd5e783..13ea451 100644 --- a/validator.go +++ b/validator.go @@ -22,7 +22,6 @@ import ( const ( utf8HexComma = "0x2C" utf8Pipe = "0x7C" - namespaceSeparator = "." tagSeparator = "," orSeparator = "|" tagKeySeparator = "=" @@ -32,8 +31,8 @@ const ( diveTag = "dive" existsTag = "exists" fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" - arrayIndexFieldName = "%s[%d]" - mapIndexFieldName = "%s[%v]" + arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket + mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket invalidValidation = "Invalid validation tag on field %s" undefinedValidation = "Undefined validation function on field %s" ) @@ -264,7 +263,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - current, kind := v.determineType(current) + current, kind := v.extractType(current) var typ reflect.Type switch kind { @@ -293,7 +292,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - // fmt.Println(kind) errs[errPrefix+name] = &FieldError{ Field: name, Tag: vals[0], @@ -314,7 +312,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. typ = current.Type() if typ != timeType && typ != timePtrType { - // goto FALLTHROUGH // required passed validation above so stop here // if only validating the structs existance. @@ -325,13 +322,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false) return } - // FALLTHROUGH: - // fallthrough - // default: - // fmt.Println(tag) - // if len(tag) == 0 { - // return - // } } if len(tag) == 0 { @@ -339,119 +329,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. } typ = current.Type() - // fmt.Println("Kind:", k) - - // kind := current.Kind() - - // if kind == reflect.Ptr && !current.IsNil() { - // current = current.Elem() - // kind = current.Kind() - // } - - // this also allows for tags 'required' and 'omitempty' to be used on - // nested struct fields because when len(tags) > 0 below and the value is nil - // then required failes and we check for omitempty just before that - // if ((kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil()) || kind == reflect.Invalid { - - // if strings.Contains(tag, omitempty) { - // return - // } - - // if len(tag) > 0 { - - // tags := strings.Split(tag, tagSeparator) - // var param string - // vals := strings.SplitN(tags[0], tagKeySeparator, 2) - - // if len(vals) > 1 { - // param = vals[1] - // } - - // if kind == reflect.Invalid { - // errs[errPrefix+name] = &FieldError{ - // Field: name, - // Tag: vals[0], - // Param: param, - // Kind: kind, - // } - // return - // } - - // errs[errPrefix+name] = &FieldError{ - // Field: name, - // Tag: vals[0], - // Param: param, - // Value: current.Interface(), - // Kind: kind, - // Type: current.Type(), - // } - - // return - // } - // // if we get here tag length is zero and we can leave - // if kind == reflect.Invalid { - // return - // } - // } - - // typ := current.Type() - - // switch kind { - // case reflect.Struct, reflect.Interface: - - // if kind == reflect.Interface { - - // current = current.Elem() - // kind = current.Kind() - - // if kind == reflect.Ptr && !current.IsNil() { - // current = current.Elem() - // kind = current.Kind() - // } - - // // changed current, so have to get inner type again - // typ = current.Type() - - // if kind != reflect.Struct { - // goto FALLTHROUGH - // } - // } - - // if typ != timeType && typ != timePtrType { - - // if kind == reflect.Struct { - - // if v.config.hasCustomFuncs { - // if fn, ok := v.config.CustomTypeFuncs[typ]; ok { - // v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) - // return - // } - // } - - // // required passed validation above so stop here - // // if only validating the structs existance. - // if strings.Contains(tag, structOnlyTag) { - // return - // } - - // v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false) - // return - // } - // } - // FALLTHROUGH: - // fallthrough - // default: - // if len(tag) == 0 { - // return - // } - // } - - // if v.config.hasCustomFuncs { - // if fn, ok := v.config.CustomTypeFuncs[typ]; ok { - // v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) - // return - // } - // } tags, isCached := tagsCache.Get(tag) diff --git a/validator_test.go b/validator_test.go index d938b1b..2c9302b 100644 --- a/validator_test.go +++ b/validator_test.go @@ -301,6 +301,33 @@ func TestCrossNamespaceFieldValidation(t *testing.T) { Equal(t, ok, true) Equal(t, kind, reflect.String) Equal(t, current.String(), "9") + + inner = &Inner{ + CreatedAt: &now, + Slice: []string{"val1", "val2", "val3"}, + SliceStructs: []*SliceStruct{{Name: "name1"}, {Name: "name2"}, nil}, + SliceSlice: [][]string{{"1", "2", "3"}, {"4", "5", "6"}, {"7", "8", "9"}}, + SliceSliceStruct: [][]*SliceStruct{{{Name: "name1"}, {Name: "name2"}, {Name: "name3"}}, {{Name: "name4"}, {Name: "name5"}, {Name: "name6"}}, {{Name: "name7"}, {Name: "name8"}, {Name: "name9"}}}, + SliceMap: []map[string]string{{"key1": "val1", "key2": "val2", "key3": "val3"}, {"key4": "val4", "key5": "val5", "key6": "val6"}}, + Map: map[string]string{"key1": "val1", "key2": "val2", "key3": "val3"}, + MapStructs: map[string]*SliceStruct{"key1": {Name: "name1"}, "key2": {Name: "name2"}, "key3": {Name: "name3"}}, + MapMap: map[string]map[string]string{"key1": {"key1-1": "val1"}, "key2": {"key2-1": "val2"}, "key3": {"key3-1": "val3"}}, + MapMapStruct: map[string]map[string]*SliceStruct{"key1": {"key1-1": {Name: "name1"}}, "key2": {"key2-1": {Name: "name2"}}, "key3": {"key3-1": {Name: "name3"}}}, + MapSlice: map[string][]string{"key1": {"1", "2", "3"}, "key2": {"4", "5", "6"}, "key3": {"7", "8", "9"}}, + } + + test = &Test{ + Inner: inner, + CreatedAt: nil, + } + + val = reflect.ValueOf(test) + + current, kind, ok = validate.getStructFieldOK(val, "Inner.SliceStructs[2]") + Equal(t, ok, true) + Equal(t, kind, reflect.Ptr) + Equal(t, current.String(), "<*validator.SliceStruct Value>") + Equal(t, current.IsNil(), true) } func TestExistsValidation(t *testing.T) {