Merge pull request #523 from go-playground/fixes

Fixes
pull/529/head
Dean Karn 5 years ago committed by GitHub
commit 883a9e06c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      Makefile
  2. 76
      baked_in.go
  3. 235
      benchmarks_test.go
  4. 27
      cache.go
  5. 10
      field_level.go
  6. 2
      translations/ja/ja.go
  7. 17
      translations/zh/zh.go
  8. 4
      translations/zh/zh_test.go
  9. 21
      translations/zh_tw/zh_tw.go
  10. 4
      translations/zh_tw/zh_tw_test.go
  11. 16
      validator.go
  12. 41
      validator_instance.go
  13. 281
      validator_test.go

@ -1,11 +1,13 @@
GOCMD=go GOCMD=go
linters-install: linters-install:
$(GOCMD) get -u github.com/alecthomas/gometalinter @golangci-lint --version >/dev/null 2>&1 || { \
gometalinter --install echo "installing linting tools..."; \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.19.1; \
}
lint: linters-install lint: linters-install
gometalinter --vendor --disable-all --enable=vet --enable=vetshadow --enable=golint --enable=maligned --enable=megacheck --enable=ineffassign --enable=misspell --enable=errcheck --enable=goconst ./... golangci-lint run
test: test:
$(GOCMD) test -cover -race ./... $(GOCMD) test -cover -race ./...

@ -1301,26 +1301,35 @@ func isDefault(fl FieldLevel) bool {
// HasValue is the validation function for validating if the current field's value is not the default static value. // HasValue is the validation function for validating if the current field's value is not the default static value.
func hasValue(fl FieldLevel) bool { func hasValue(fl FieldLevel) bool {
return requireCheckFieldKind(fl, "") field := fl.Field()
switch field.Kind() {
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return !field.IsNil()
default:
if fl.(*validate).fldIsPointer && field.Interface() != nil {
return true
}
return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
}
} }
// requireCheckField is a func for check field kind // requireCheckField is a func for check field kind
func requireCheckFieldKind(fl FieldLevel, param string) bool { func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool {
field := fl.Field() field := fl.Field()
var ok bool
kind := field.Kind()
if len(param) > 0 { if len(param) > 0 {
if fl.Parent().Kind() == reflect.Ptr { field, kind, ok = fl.GetStructFieldOKAdvanced(fl.Parent(), param)
field = fl.Parent().Elem().FieldByName(param) if !ok {
} else { return defaultNotFoundValue
field = fl.Parent().FieldByName(param)
} }
} }
switch field.Kind() { switch kind {
case reflect.Invalid:
return defaultNotFoundValue
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return !field.IsNil() return !field.IsNil()
default: default:
if fl.(*validate).fldIsPointer && field.Interface() != nil {
return true
}
return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface() return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
} }
} }
@ -1328,76 +1337,49 @@ func requireCheckFieldKind(fl FieldLevel, param string) bool {
// RequiredWith is the validation function // RequiredWith is the validation function
// The field under validation must be present and not empty only if any of the other specified fields are present. // The field under validation must be present and not empty only if any of the other specified fields are present.
func requiredWith(fl FieldLevel) bool { func requiredWith(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param()) params := parseOneOfParam2(fl.Param())
for _, param := range params { for _, param := range params {
if requireCheckFieldKind(fl, param, false) {
if requireCheckFieldKind(fl, param) { return hasValue(fl)
return requireCheckFieldKind(fl, "")
} }
} }
return true return true
} }
// RequiredWithAll is the validation function // RequiredWithAll is the validation function
// The field under validation must be present and not empty only if all of the other specified fields are present. // The field under validation must be present and not empty only if all of the other specified fields are present.
func requiredWithAll(fl FieldLevel) bool { func requiredWithAll(fl FieldLevel) bool {
isValidateCurrentField := true
params := parseOneOfParam2(fl.Param()) params := parseOneOfParam2(fl.Param())
for _, param := range params { for _, param := range params {
if !requireCheckFieldKind(fl, param, false) {
if !requireCheckFieldKind(fl, param) { return true
isValidateCurrentField = false
}
} }
if isValidateCurrentField {
return requireCheckFieldKind(fl, "")
} }
return hasValue(fl)
return true
} }
// RequiredWithout is the validation function // RequiredWithout is the validation function
// The field under validation must be present and not empty only when any of the other specified fields are not present. // The field under validation must be present and not empty only when any of the other specified fields are not present.
func requiredWithout(fl FieldLevel) bool { func requiredWithout(fl FieldLevel) bool {
isValidateCurrentField := false
params := parseOneOfParam2(fl.Param()) params := parseOneOfParam2(fl.Param())
for _, param := range params { for _, param := range params {
if !requireCheckFieldKind(fl, param, true) {
if requireCheckFieldKind(fl, param) { return hasValue(fl)
isValidateCurrentField = true
} }
} }
if !isValidateCurrentField {
return requireCheckFieldKind(fl, "")
}
return true return true
} }
// RequiredWithoutAll is the validation function // RequiredWithoutAll is the validation function
// The field under validation must be present and not empty only when all of the other specified fields are not present. // The field under validation must be present and not empty only when all of the other specified fields are not present.
func requiredWithoutAll(fl FieldLevel) bool { func requiredWithoutAll(fl FieldLevel) bool {
isValidateCurrentField := true
params := parseOneOfParam2(fl.Param()) params := parseOneOfParam2(fl.Param())
for _, param := range params { for _, param := range params {
if requireCheckFieldKind(fl, param, true) {
if requireCheckFieldKind(fl, param) { return true
isValidateCurrentField = false
}
} }
if isValidateCurrentField {
return requireCheckFieldKind(fl, "")
} }
return hasValue(fl)
return true
} }
// IsGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value. // IsGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value.

@ -8,236 +8,200 @@ import (
) )
func BenchmarkFieldSuccess(b *testing.B) { func BenchmarkFieldSuccess(b *testing.B) {
validate := New() validate := New()
s := "1" s := "1"
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(&s, "len=1") _ = validate.Var(&s, "len=1")
} }
} }
func BenchmarkFieldSuccessParallel(b *testing.B) { func BenchmarkFieldSuccessParallel(b *testing.B) {
validate := New() validate := New()
s := "1" s := "1"
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(&s, "len=1") _ = validate.Var(&s, "len=1")
} }
}) })
} }
func BenchmarkFieldFailure(b *testing.B) { func BenchmarkFieldFailure(b *testing.B) {
validate := New() validate := New()
s := "12" s := "12"
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(&s, "len=1") _ = validate.Var(&s, "len=1")
} }
} }
func BenchmarkFieldFailureParallel(b *testing.B) { func BenchmarkFieldFailureParallel(b *testing.B) {
validate := New() validate := New()
s := "12" s := "12"
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(&s, "len=1") _ = validate.Var(&s, "len=1")
} }
}) })
} }
func BenchmarkFieldArrayDiveSuccess(b *testing.B) { func BenchmarkFieldArrayDiveSuccess(b *testing.B) {
validate := New() validate := New()
m := []string{"val1", "val2", "val3"} m := []string{"val1", "val2", "val3"}
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(m, "required,dive,required") _ = validate.Var(m, "required,dive,required")
} }
} }
func BenchmarkFieldArrayDiveSuccessParallel(b *testing.B) { func BenchmarkFieldArrayDiveSuccessParallel(b *testing.B) {
validate := New() validate := New()
m := []string{"val1", "val2", "val3"} m := []string{"val1", "val2", "val3"}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(m, "required,dive,required") _ = validate.Var(m, "required,dive,required")
} }
}) })
} }
func BenchmarkFieldArrayDiveFailure(b *testing.B) { func BenchmarkFieldArrayDiveFailure(b *testing.B) {
validate := New() validate := New()
m := []string{"val1", "", "val3"} m := []string{"val1", "", "val3"}
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(m, "required,dive,required") _ = validate.Var(m, "required,dive,required")
} }
} }
func BenchmarkFieldArrayDiveFailureParallel(b *testing.B) { func BenchmarkFieldArrayDiveFailureParallel(b *testing.B) {
validate := New() validate := New()
m := []string{"val1", "", "val3"} m := []string{"val1", "", "val3"}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(m, "required,dive,required") _ = validate.Var(m, "required,dive,required")
} }
}) })
} }
func BenchmarkFieldMapDiveSuccess(b *testing.B) { func BenchmarkFieldMapDiveSuccess(b *testing.B) {
validate := New() validate := New()
m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"} m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"}
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(m, "required,dive,required") _ = validate.Var(m, "required,dive,required")
} }
} }
func BenchmarkFieldMapDiveSuccessParallel(b *testing.B) { func BenchmarkFieldMapDiveSuccessParallel(b *testing.B) {
validate := New() validate := New()
m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"} m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(m, "required,dive,required") _ = validate.Var(m, "required,dive,required")
} }
}) })
} }
func BenchmarkFieldMapDiveFailure(b *testing.B) { func BenchmarkFieldMapDiveFailure(b *testing.B) {
validate := New() validate := New()
m := map[string]string{"": "", "val3": "val3"} m := map[string]string{"": "", "val3": "val3"}
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(m, "required,dive,required") _ = validate.Var(m, "required,dive,required")
} }
} }
func BenchmarkFieldMapDiveFailureParallel(b *testing.B) { func BenchmarkFieldMapDiveFailureParallel(b *testing.B) {
validate := New() validate := New()
m := map[string]string{"": "", "val3": "val3"} m := map[string]string{"": "", "val3": "val3"}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(m, "required,dive,required") _ = validate.Var(m, "required,dive,required")
} }
}) })
} }
func BenchmarkFieldMapDiveWithKeysSuccess(b *testing.B) { func BenchmarkFieldMapDiveWithKeysSuccess(b *testing.B) {
validate := New() validate := New()
m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"} m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"}
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(m, "required,dive,keys,required,endkeys,required") _ = validate.Var(m, "required,dive,keys,required,endkeys,required")
} }
} }
func BenchmarkFieldMapDiveWithKeysSuccessParallel(b *testing.B) { func BenchmarkFieldMapDiveWithKeysSuccessParallel(b *testing.B) {
validate := New() validate := New()
m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"} m := map[string]string{"val1": "val1", "val2": "val2", "val3": "val3"}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(m, "required,dive,keys,required,endkeys,required") _ = validate.Var(m, "required,dive,keys,required,endkeys,required")
} }
}) })
} }
func BenchmarkFieldMapDiveWithKeysFailure(b *testing.B) { func BenchmarkFieldMapDiveWithKeysFailure(b *testing.B) {
validate := New() validate := New()
m := map[string]string{"": "", "val3": "val3"} m := map[string]string{"": "", "val3": "val3"}
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(m, "required,dive,keys,required,endkeys,required") _ = validate.Var(m, "required,dive,keys,required,endkeys,required")
} }
} }
func BenchmarkFieldMapDiveWithKeysFailureParallel(b *testing.B) { func BenchmarkFieldMapDiveWithKeysFailureParallel(b *testing.B) {
validate := New() validate := New()
m := map[string]string{"": "", "val3": "val3"} m := map[string]string{"": "", "val3": "val3"}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(m, "required,dive,keys,required,endkeys,required") _ = validate.Var(m, "required,dive,keys,required,endkeys,required")
} }
}) })
} }
func BenchmarkFieldCustomTypeSuccess(b *testing.B) { func BenchmarkFieldCustomTypeSuccess(b *testing.B) {
validate := New() validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{ val := valuer{
Name: "1", Name: "1",
} }
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(val, "len=1") _ = validate.Var(val, "len=1")
} }
} }
func BenchmarkFieldCustomTypeSuccessParallel(b *testing.B) { func BenchmarkFieldCustomTypeSuccessParallel(b *testing.B) {
validate := New() validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{ val := valuer{
Name: "1", Name: "1",
} }
@ -245,93 +209,80 @@ func BenchmarkFieldCustomTypeSuccessParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(val, "len=1") _ = validate.Var(val, "len=1")
} }
}) })
} }
func BenchmarkFieldCustomTypeFailure(b *testing.B) { func BenchmarkFieldCustomTypeFailure(b *testing.B) {
validate := New() validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{} val := valuer{}
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(val, "len=1") _ = validate.Var(val, "len=1")
} }
} }
func BenchmarkFieldCustomTypeFailureParallel(b *testing.B) { func BenchmarkFieldCustomTypeFailureParallel(b *testing.B) {
validate := New() validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{} val := valuer{}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(val, "len=1") _ = validate.Var(val, "len=1")
} }
}) })
} }
func BenchmarkFieldOrTagSuccess(b *testing.B) { func BenchmarkFieldOrTagSuccess(b *testing.B) {
validate := New() validate := New()
s := "rgba(0,0,0,1)" s := "rgba(0,0,0,1)"
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(s, "rgb|rgba") _ = validate.Var(s, "rgb|rgba")
} }
} }
func BenchmarkFieldOrTagSuccessParallel(b *testing.B) { func BenchmarkFieldOrTagSuccessParallel(b *testing.B) {
validate := New() validate := New()
s := "rgba(0,0,0,1)" s := "rgba(0,0,0,1)"
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(s, "rgb|rgba") _ = validate.Var(s, "rgb|rgba")
} }
}) })
} }
func BenchmarkFieldOrTagFailure(b *testing.B) { func BenchmarkFieldOrTagFailure(b *testing.B) {
validate := New() validate := New()
s := "#000" s := "#000"
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Var(s, "rgb|rgba") _ = validate.Var(s, "rgb|rgba")
} }
} }
func BenchmarkFieldOrTagFailureParallel(b *testing.B) { func BenchmarkFieldOrTagFailureParallel(b *testing.B) {
validate := New() validate := New()
s := "#000" s := "#000"
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Var(s, "rgb|rgba") _ = validate.Var(s, "rgb|rgba")
} }
}) })
} }
func BenchmarkStructLevelValidationSuccess(b *testing.B) { func BenchmarkStructLevelValidationSuccess(b *testing.B) {
validate := New() validate := New()
validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{}) validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{})
@ -341,12 +292,11 @@ func BenchmarkStructLevelValidationSuccess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tst) _ = validate.Struct(tst)
} }
} }
func BenchmarkStructLevelValidationSuccessParallel(b *testing.B) { func BenchmarkStructLevelValidationSuccessParallel(b *testing.B) {
validate := New() validate := New()
validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{}) validate.RegisterStructValidation(StructValidationTestStructSuccess, TestStruct{})
@ -357,13 +307,12 @@ func BenchmarkStructLevelValidationSuccessParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(tst) _ = validate.Struct(tst)
} }
}) })
} }
func BenchmarkStructLevelValidationFailure(b *testing.B) { func BenchmarkStructLevelValidationFailure(b *testing.B) {
validate := New() validate := New()
validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{}) validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{})
@ -373,12 +322,11 @@ func BenchmarkStructLevelValidationFailure(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tst) _ = validate.Struct(tst)
} }
} }
func BenchmarkStructLevelValidationFailureParallel(b *testing.B) { func BenchmarkStructLevelValidationFailureParallel(b *testing.B) {
validate := New() validate := New()
validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{}) validate.RegisterStructValidation(StructValidationTestStruct, TestStruct{})
@ -389,13 +337,12 @@ func BenchmarkStructLevelValidationFailureParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(tst) _ = validate.Struct(tst)
} }
}) })
} }
func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
validate := New() validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
@ -412,15 +359,13 @@ func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(validFoo) _ = validate.Struct(validFoo)
} }
} }
func BenchmarkStructSimpleCustomTypeSuccessParallel(b *testing.B) { func BenchmarkStructSimpleCustomTypeSuccessParallel(b *testing.B) {
validate := New() validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
val := valuer{ val := valuer{
Name: "1", Name: "1",
} }
@ -429,19 +374,17 @@ func BenchmarkStructSimpleCustomTypeSuccessParallel(b *testing.B) {
Valuer valuer `validate:"len=1"` Valuer valuer `validate:"len=1"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
} }
validFoo := &Foo{Valuer: val, IntValue: 7} validFoo := &Foo{Valuer: val, IntValue: 7}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(validFoo) _ = validate.Struct(validFoo)
} }
}) })
} }
func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
validate := New() validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
@ -451,17 +394,15 @@ func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) {
Valuer valuer `validate:"len=1"` Valuer valuer `validate:"len=1"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
} }
validFoo := &Foo{Valuer: val, IntValue: 3} validFoo := &Foo{Valuer: val, IntValue: 3}
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(validFoo) _ = validate.Struct(validFoo)
} }
} }
func BenchmarkStructSimpleCustomTypeFailureParallel(b *testing.B) { func BenchmarkStructSimpleCustomTypeFailureParallel(b *testing.B) {
validate := New() validate := New()
validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{})
@ -471,19 +412,17 @@ func BenchmarkStructSimpleCustomTypeFailureParallel(b *testing.B) {
Valuer valuer `validate:"len=1"` Valuer valuer `validate:"len=1"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
} }
validFoo := &Foo{Valuer: val, IntValue: 3} validFoo := &Foo{Valuer: val, IntValue: 3}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(validate.Struct(validFoo)) _ = validate.Struct(validate.Struct(validFoo))
} }
}) })
} }
func BenchmarkStructFilteredSuccess(b *testing.B) { func BenchmarkStructFilteredSuccess(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -494,21 +433,18 @@ func BenchmarkStructFilteredSuccess(b *testing.B) {
test := &Test{ test := &Test{
Name: "Joey Bloggs", Name: "Joey Bloggs",
} }
byts := []byte("Name") byts := []byte("Name")
fn := func(ns []byte) bool { fn := func(ns []byte) bool {
return !bytes.HasSuffix(ns, byts) return !bytes.HasSuffix(ns, byts)
} }
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructFiltered(test, fn) _ = validate.StructFiltered(test, fn)
} }
} }
func BenchmarkStructFilteredSuccessParallel(b *testing.B) { func BenchmarkStructFilteredSuccessParallel(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -519,9 +455,7 @@ func BenchmarkStructFilteredSuccessParallel(b *testing.B) {
test := &Test{ test := &Test{
Name: "Joey Bloggs", Name: "Joey Bloggs",
} }
byts := []byte("Name") byts := []byte("Name")
fn := func(ns []byte) bool { fn := func(ns []byte) bool {
return !bytes.HasSuffix(ns, byts) return !bytes.HasSuffix(ns, byts)
} }
@ -529,13 +463,12 @@ func BenchmarkStructFilteredSuccessParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.StructFiltered(test, fn) _ = validate.StructFiltered(test, fn)
} }
}) })
} }
func BenchmarkStructFilteredFailure(b *testing.B) { func BenchmarkStructFilteredFailure(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -555,12 +488,11 @@ func BenchmarkStructFilteredFailure(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructFiltered(test, fn) _ = validate.StructFiltered(test, fn)
} }
} }
func BenchmarkStructFilteredFailureParallel(b *testing.B) { func BenchmarkStructFilteredFailureParallel(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -571,9 +503,7 @@ func BenchmarkStructFilteredFailureParallel(b *testing.B) {
test := &Test{ test := &Test{
Name: "Joey Bloggs", Name: "Joey Bloggs",
} }
byts := []byte("NickName") byts := []byte("NickName")
fn := func(ns []byte) bool { fn := func(ns []byte) bool {
return !bytes.HasSuffix(ns, byts) return !bytes.HasSuffix(ns, byts)
} }
@ -581,13 +511,12 @@ func BenchmarkStructFilteredFailureParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.StructFiltered(test, fn) _ = validate.StructFiltered(test, fn)
} }
}) })
} }
func BenchmarkStructPartialSuccess(b *testing.B) { func BenchmarkStructPartialSuccess(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -601,12 +530,11 @@ func BenchmarkStructPartialSuccess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructPartial(test, "Name") _ = validate.StructPartial(test, "Name")
} }
} }
func BenchmarkStructPartialSuccessParallel(b *testing.B) { func BenchmarkStructPartialSuccessParallel(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -621,13 +549,12 @@ func BenchmarkStructPartialSuccessParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.StructPartial(test, "Name") _ = validate.StructPartial(test, "Name")
} }
}) })
} }
func BenchmarkStructPartialFailure(b *testing.B) { func BenchmarkStructPartialFailure(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -641,12 +568,11 @@ func BenchmarkStructPartialFailure(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructPartial(test, "NickName") _ = validate.StructPartial(test, "NickName")
} }
} }
func BenchmarkStructPartialFailureParallel(b *testing.B) { func BenchmarkStructPartialFailureParallel(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -661,13 +587,12 @@ func BenchmarkStructPartialFailureParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.StructPartial(test, "NickName") _ = validate.StructPartial(test, "NickName")
} }
}) })
} }
func BenchmarkStructExceptSuccess(b *testing.B) { func BenchmarkStructExceptSuccess(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -681,12 +606,11 @@ func BenchmarkStructExceptSuccess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructExcept(test, "Nickname") _ = validate.StructExcept(test, "Nickname")
} }
} }
func BenchmarkStructExceptSuccessParallel(b *testing.B) { func BenchmarkStructExceptSuccessParallel(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -701,13 +625,12 @@ func BenchmarkStructExceptSuccessParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.StructExcept(test, "NickName") _ = validate.StructExcept(test, "NickName")
} }
}) })
} }
func BenchmarkStructExceptFailure(b *testing.B) { func BenchmarkStructExceptFailure(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -721,12 +644,11 @@ func BenchmarkStructExceptFailure(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.StructExcept(test, "Name") _ = validate.StructExcept(test, "Name")
} }
} }
func BenchmarkStructExceptFailureParallel(b *testing.B) { func BenchmarkStructExceptFailureParallel(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -741,13 +663,12 @@ func BenchmarkStructExceptFailureParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.StructExcept(test, "Name") _ = validate.StructExcept(test, "Name")
} }
}) })
} }
func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) { func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -757,7 +678,6 @@ func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) {
now := time.Now().UTC() now := time.Now().UTC()
then := now.Add(time.Hour * 5) then := now.Add(time.Hour * 5)
test := &Test{ test := &Test{
Start: now, Start: now,
End: then, End: then,
@ -765,12 +685,11 @@ func BenchmarkStructSimpleCrossFieldSuccess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(test) _ = validate.Struct(test)
} }
} }
func BenchmarkStructSimpleCrossFieldSuccessParallel(b *testing.B) { func BenchmarkStructSimpleCrossFieldSuccessParallel(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -780,7 +699,6 @@ func BenchmarkStructSimpleCrossFieldSuccessParallel(b *testing.B) {
now := time.Now().UTC() now := time.Now().UTC()
then := now.Add(time.Hour * 5) then := now.Add(time.Hour * 5)
test := &Test{ test := &Test{
Start: now, Start: now,
End: then, End: then,
@ -789,13 +707,12 @@ func BenchmarkStructSimpleCrossFieldSuccessParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(test) _ = validate.Struct(test)
} }
}) })
} }
func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) { func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -813,12 +730,11 @@ func BenchmarkStructSimpleCrossFieldFailure(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(test) _ = validate.Struct(test)
} }
} }
func BenchmarkStructSimpleCrossFieldFailureParallel(b *testing.B) { func BenchmarkStructSimpleCrossFieldFailureParallel(b *testing.B) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -828,7 +744,6 @@ func BenchmarkStructSimpleCrossFieldFailureParallel(b *testing.B) {
now := time.Now().UTC() now := time.Now().UTC()
then := now.Add(time.Hour * -5) then := now.Add(time.Hour * -5)
test := &Test{ test := &Test{
Start: now, Start: now,
End: then, End: then,
@ -836,13 +751,12 @@ func BenchmarkStructSimpleCrossFieldFailureParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(test) _ = validate.Struct(test)
} }
}) })
} }
func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) { func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) {
validate := New() validate := New()
type Inner struct { type Inner struct {
@ -855,11 +769,9 @@ func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) {
} }
now := time.Now().UTC() now := time.Now().UTC()
inner := &Inner{ inner := &Inner{
Start: now, Start: now,
} }
outer := &Outer{ outer := &Outer{
Inner: inner, Inner: inner,
CreatedAt: now, CreatedAt: now,
@ -867,12 +779,11 @@ func BenchmarkStructSimpleCrossStructCrossFieldSuccess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(outer) _ = validate.Struct(outer)
} }
} }
func BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel(b *testing.B) { func BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel(b *testing.B) {
validate := New() validate := New()
type Inner struct { type Inner struct {
@ -885,11 +796,9 @@ func BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel(b *testing.B) {
} }
now := time.Now().UTC() now := time.Now().UTC()
inner := &Inner{ inner := &Inner{
Start: now, Start: now,
} }
outer := &Outer{ outer := &Outer{
Inner: inner, Inner: inner,
CreatedAt: now, CreatedAt: now,
@ -898,15 +807,13 @@ func BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(outer) _ = validate.Struct(outer)
} }
}) })
} }
func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) { func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) {
validate := New() validate := New()
type Inner struct { type Inner struct {
Start time.Time Start time.Time
} }
@ -930,12 +837,11 @@ func BenchmarkStructSimpleCrossStructCrossFieldFailure(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(outer) _ = validate.Struct(outer)
} }
} }
func BenchmarkStructSimpleCrossStructCrossFieldFailureParallel(b *testing.B) { func BenchmarkStructSimpleCrossStructCrossFieldFailureParallel(b *testing.B) {
validate := New() validate := New()
type Inner struct { type Inner struct {
@ -962,15 +868,13 @@ func BenchmarkStructSimpleCrossStructCrossFieldFailureParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(outer) _ = validate.Struct(outer)
} }
}) })
} }
func BenchmarkStructSimpleSuccess(b *testing.B) { func BenchmarkStructSimpleSuccess(b *testing.B) {
validate := New() validate := New()
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
@ -980,33 +884,28 @@ func BenchmarkStructSimpleSuccess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(validFoo) _ = validate.Struct(validFoo)
} }
} }
func BenchmarkStructSimpleSuccessParallel(b *testing.B) { func BenchmarkStructSimpleSuccessParallel(b *testing.B) {
validate := New() validate := New()
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
} }
validFoo := &Foo{StringValue: "Foobar", IntValue: 7} validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(validFoo) _ = validate.Struct(validFoo)
} }
}) })
} }
func BenchmarkStructSimpleFailure(b *testing.B) { func BenchmarkStructSimpleFailure(b *testing.B) {
validate := New() validate := New()
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
@ -1016,14 +915,12 @@ func BenchmarkStructSimpleFailure(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(invalidFoo) _ = validate.Struct(invalidFoo)
} }
} }
func BenchmarkStructSimpleFailureParallel(b *testing.B) { func BenchmarkStructSimpleFailureParallel(b *testing.B) {
validate := New() validate := New()
type Foo struct { type Foo struct {
StringValue string `validate:"min=5,max=10"` StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"`
@ -1034,15 +931,13 @@ func BenchmarkStructSimpleFailureParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(invalidFoo) _ = validate.Struct(invalidFoo)
} }
}) })
} }
func BenchmarkStructComplexSuccess(b *testing.B) { func BenchmarkStructComplexSuccess(b *testing.B) {
validate := New() validate := New()
tSuccess := &TestString{ tSuccess := &TestString{
Required: "Required", Required: "Required",
Len: "length==10", Len: "length==10",
@ -1072,14 +967,12 @@ func BenchmarkStructComplexSuccess(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tSuccess) _ = validate.Struct(tSuccess)
} }
} }
func BenchmarkStructComplexSuccessParallel(b *testing.B) { func BenchmarkStructComplexSuccessParallel(b *testing.B) {
validate := New() validate := New()
tSuccess := &TestString{ tSuccess := &TestString{
Required: "Required", Required: "Required",
Len: "length==10", Len: "length==10",
@ -1110,15 +1003,13 @@ func BenchmarkStructComplexSuccessParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(tSuccess) _ = validate.Struct(tSuccess)
} }
}) })
} }
func BenchmarkStructComplexFailure(b *testing.B) { func BenchmarkStructComplexFailure(b *testing.B) {
validate := New() validate := New()
tFail := &TestString{ tFail := &TestString{
Required: "", Required: "",
Len: "", Len: "",
@ -1145,14 +1036,12 @@ func BenchmarkStructComplexFailure(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tFail) _ = validate.Struct(tFail)
} }
} }
func BenchmarkStructComplexFailureParallel(b *testing.B) { func BenchmarkStructComplexFailureParallel(b *testing.B) {
validate := New() validate := New()
tFail := &TestString{ tFail := &TestString{
Required: "", Required: "",
Len: "", Len: "",
@ -1180,7 +1069,7 @@ func BenchmarkStructComplexFailureParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
validate.Struct(tFail) _ = validate.Struct(tFail)
} }
}) })
} }
@ -1193,7 +1082,7 @@ func BenchmarkOneof(b *testing.B) {
w := &TestOneof{Color: "green"} w := &TestOneof{Color: "green"}
val := New() val := New()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
val.Struct(w) _ = val.Struct(w)
} }
} }
@ -1204,7 +1093,7 @@ func BenchmarkOneofParallel(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
val.Struct(w) _ = val.Struct(w)
} }
}) })
} }

@ -39,9 +39,7 @@ func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
} }
func (sc *structCache) Set(key reflect.Type, value *cStruct) { func (sc *structCache) Set(key reflect.Type, value *cStruct) {
m := sc.m.Load().(map[reflect.Type]*cStruct) m := sc.m.Load().(map[reflect.Type]*cStruct)
nm := make(map[reflect.Type]*cStruct, len(m)+1) nm := make(map[reflect.Type]*cStruct, len(m)+1)
for k, v := range m { for k, v := range m {
nm[k] = v nm[k] = v
@ -61,9 +59,7 @@ func (tc *tagCache) Get(key string) (c *cTag, found bool) {
} }
func (tc *tagCache) Set(key string, value *cTag) { func (tc *tagCache) Set(key string, value *cTag) {
m := tc.m.Load().(map[string]*cTag) m := tc.m.Load().(map[string]*cTag)
nm := make(map[string]*cTag, len(m)+1) nm := make(map[string]*cTag, len(m)+1)
for k, v := range m { for k, v := range m {
nm[k] = v nm[k] = v
@ -99,10 +95,10 @@ type cTag struct {
hasAlias bool hasAlias bool
hasParam bool // true if parameter used eg. eq= where the equal sign has been set hasParam bool // true if parameter used eg. eq= where the equal sign has been set
isBlockEnd bool // indicates the current tag represents the last validation in the block isBlockEnd bool // indicates the current tag represents the last validation in the block
runValidationWhenNil bool
} }
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct { func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
v.structCache.lock.Lock() v.structCache.lock.Lock()
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise! defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
@ -141,9 +137,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
customName = fld.Name customName = fld.Name
if v.hasTagNameFunc { if v.hasTagNameFunc {
name := v.tagNameFunc(fld) name := v.tagNameFunc(fld)
if len(name) > 0 { if len(name) > 0 {
customName = name customName = name
} }
@ -168,23 +162,17 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
namesEqual: fld.Name == customName, namesEqual: fld.Name == customName,
}) })
} }
v.structCache.Set(typ, cs) v.structCache.Set(typ, cs)
return cs return cs
} }
func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) { func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
var t string var t string
var ok bool
noAlias := len(alias) == 0 noAlias := len(alias) == 0
tags := strings.Split(tag, tagSeparator) tags := strings.Split(tag, tagSeparator)
for i := 0; i < len(tags); i++ { for i := 0; i < len(tags); i++ {
t = tags[i] t = tags[i]
if noAlias { if noAlias {
alias = t alias = t
} }
@ -198,14 +186,13 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
current.next, current = next, curr current.next, current = next, curr
} }
continue continue
} }
var prevTag tagType var prevTag tagType
if i == 0 { if i == 0 {
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true} current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true, typeof: typeDefault}
firstCtag = current firstCtag = current
} else { } else {
prevTag = current.typeof prevTag = current.typeof
@ -214,7 +201,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
} }
switch t { switch t {
case diveTag: case diveTag:
current.typeof = typeDive current.typeof = typeDive
continue continue
@ -270,18 +256,14 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
continue continue
default: default:
if t == isdefault { if t == isdefault {
current.typeof = typeIsDefault current.typeof = typeIsDefault
} }
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C" // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator) orVals := strings.Split(t, orSeparator)
for j := 0; j < len(orVals); j++ { for j := 0; j < len(orVals); j++ {
vals := strings.SplitN(orVals[j], tagKeySeparator, 2) vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
if noAlias { if noAlias {
alias = vals[0] alias = vals[0]
current.aliasTag = alias current.aliasTag = alias
@ -300,7 +282,10 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName))) panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
} }
if current.fn, ok = v.validations[current.tag]; !ok { if wrapper, ok := v.validations[current.tag]; ok {
current.fn = wrapper.fn
current.runValidationWhenNil = wrapper.runValidatinOnNil
} else {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName))) panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName)))
} }

@ -38,6 +38,10 @@ type FieldLevel interface {
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist. // could not be retrieved because it didn't exist.
GetStructFieldOK() (reflect.Value, reflect.Kind, bool) GetStructFieldOK() (reflect.Value, reflect.Kind, bool)
// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool)
} }
var _ FieldLevel = new(validate) var _ FieldLevel = new(validate)
@ -67,3 +71,9 @@ func (v *validate) Param() string {
func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) { func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) {
return v.getStructFieldOKInternal(v.slflParent, v.ct.param) return v.getStructFieldOKInternal(v.slflParent, v.ct.param)
} }
// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
func (v *validate) GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
return v.getStructFieldOKInternal(val, namespace)
}

@ -374,9 +374,7 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
if err != nil { if err != nil {
goto END goto END
} }
t, err = ut.T("ne-items", fe.Field(), c) t, err = ut.T("ne-items", fe.Field(), c)
break
default: default:
t, err = ut.T("ne", fe.Field(), fe.Param()) t, err = ut.T("ne", fe.Field(), fe.Param())
} }

@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/go-playground/locales" "github.com/go-playground/locales"
"github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
"gopkg.in/go-playground/validator.v9" "gopkg.in/go-playground/validator.v9"
) )
@ -430,9 +430,9 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s'不能用于struct类型.", fe.Tag()) err = fmt.Errorf("tag '%s'不能用于struct类型.", fe.Tag())
} } else {
t, err = ut.T("lt-datetime", fe.Field()) t, err = ut.T("lt-datetime", fe.Field())
}
default: default:
err = fn() err = fn()
@ -549,9 +549,9 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s'不能用于struct类型.", fe.Tag()) err = fmt.Errorf("tag '%s'不能用于struct类型.", fe.Tag())
} } else {
t, err = ut.T("lte-datetime", fe.Field()) t, err = ut.T("lte-datetime", fe.Field())
}
default: default:
err = fn() err = fn()
@ -668,9 +668,10 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s'不能用于struct类型.", fe.Tag()) err = fmt.Errorf("tag '%s'不能用于struct类型.", fe.Tag())
} } else {
t, err = ut.T("gt-datetime", fe.Field()) t, err = ut.T("gt-datetime", fe.Field())
}
default: default:
err = fn() err = fn()
@ -787,9 +788,9 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s'不能用于struct类型.", fe.Tag()) err = fmt.Errorf("tag '%s'不能用于struct类型.", fe.Tag())
} } else {
t, err = ut.T("gte-datetime", fe.Field()) t, err = ut.T("gte-datetime", fe.Field())
}
default: default:
err = fn() err = fn()

@ -5,7 +5,7 @@ import (
"time" "time"
zhongwen "github.com/go-playground/locales/zh" zhongwen "github.com/go-playground/locales/zh"
"github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
. "gopkg.in/go-playground/assert.v1" . "gopkg.in/go-playground/assert.v1"
"gopkg.in/go-playground/validator.v9" "gopkg.in/go-playground/validator.v9"
) )
@ -14,7 +14,7 @@ func TestTranslations(t *testing.T) {
zh := zhongwen.New() zh := zhongwen.New()
uni := ut.New(zh, zh) uni := ut.New(zh, zh)
trans, ok := uni.GetTranslator("zh") trans, _ := uni.GetTranslator("zh")
validate := validator.New() validate := validator.New()

@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/go-playground/locales" "github.com/go-playground/locales"
"github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
"gopkg.in/go-playground/validator.v9" "gopkg.in/go-playground/validator.v9"
) )
@ -430,9 +430,9 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s'不能用於struct類型.", fe.Tag()) err = fmt.Errorf("tag '%s'不能用於struct類型.", fe.Tag())
} } else {
t, err = ut.T("lt-datetime", fe.Field()) t, err = ut.T("lt-datetime", fe.Field())
}
default: default:
err = fn() err = fn()
@ -549,9 +549,9 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s'不能用於struct類型.", fe.Tag()) err = fmt.Errorf("tag '%s'不能用於struct類型.", fe.Tag())
} } else {
t, err = ut.T("lte-datetime", fe.Field()) t, err = ut.T("lte-datetime", fe.Field())
}
default: default:
err = fn() err = fn()
@ -618,13 +618,10 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
var kind reflect.Kind var kind reflect.Kind
fn := func() (err error) { fn := func() (err error) {
if idx := strings.Index(fe.Param(), "."); idx != -1 { if idx := strings.Index(fe.Param(), "."); idx != -1 {
digits = uint64(len(fe.Param()[idx+1:])) digits = uint64(len(fe.Param()[idx+1:]))
} }
f64, err = strconv.ParseFloat(fe.Param(), 64) f64, err = strconv.ParseFloat(fe.Param(), 64)
return return
} }
@ -668,9 +665,9 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s'不能用於struct類型.", fe.Tag()) err = fmt.Errorf("tag '%s'不能用於struct類型.", fe.Tag())
} } else {
t, err = ut.T("gt-datetime", fe.Field()) t, err = ut.T("gt-datetime", fe.Field())
}
default: default:
err = fn() err = fn()
@ -787,9 +784,9 @@ func RegisterDefaultTranslations(v *validator.Validate, trans ut.Translator) (er
case reflect.Struct: case reflect.Struct:
if fe.Type() != reflect.TypeOf(time.Time{}) { if fe.Type() != reflect.TypeOf(time.Time{}) {
err = fmt.Errorf("tag '%s'不能用於struct類型.", fe.Tag()) err = fmt.Errorf("tag '%s'不能用於struct類型.", fe.Tag())
} } else {
t, err = ut.T("gte-datetime", fe.Field()) t, err = ut.T("gte-datetime", fe.Field())
}
default: default:
err = fn() err = fn()

@ -5,7 +5,7 @@ import (
"time" "time"
zhongwen "github.com/go-playground/locales/zh_Hant_TW" zhongwen "github.com/go-playground/locales/zh_Hant_TW"
"github.com/go-playground/universal-translator" ut "github.com/go-playground/universal-translator"
. "gopkg.in/go-playground/assert.v1" . "gopkg.in/go-playground/assert.v1"
"gopkg.in/go-playground/validator.v9" "gopkg.in/go-playground/validator.v9"
) )
@ -14,7 +14,7 @@ func TestTranslations(t *testing.T) {
zh := zhongwen.New() zh := zhongwen.New()
uni := ut.New(zh, zh) uni := ut.New(zh, zh)
trans, ok := uni.GetTranslator("zh") trans, _ := uni.GetTranslator("zh")
validate := validator.New() validate := validator.New()

@ -94,7 +94,6 @@ func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, cur
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options // traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) {
var typ reflect.Type var typ reflect.Type
var kind reflect.Kind var kind reflect.Kind
@ -112,16 +111,13 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
} }
if ct.hasTag { if ct.hasTag {
if kind == reflect.Invalid {
v.str1 = string(append(ns, cf.altName...)) v.str1 = string(append(ns, cf.altName...))
if v.v.hasTagNameFunc { if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...)) v.str2 = string(append(structNs, cf.name...))
} else { } else {
v.str2 = v.str1 v.str2 = v.str1
} }
if kind == reflect.Invalid {
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v, v: v.v,
@ -135,10 +131,16 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
kind: kind, kind: kind,
}, },
) )
return return
} }
v.str1 = string(append(ns, cf.altName...))
if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
} else {
v.str2 = v.str1
}
if !ct.runValidationWhenNil {
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v, v: v.v,
@ -154,9 +156,9 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
typ: current.Type(), typ: current.Type(),
}, },
) )
return return
} }
}
case reflect.Struct: case reflect.Struct:

@ -23,6 +23,10 @@ const (
noStructLevelTag = "nostructlevel" noStructLevelTag = "nostructlevel"
omitempty = "omitempty" omitempty = "omitempty"
isdefault = "isdefault" isdefault = "isdefault"
requiredWithoutAllTag = "required_without_all"
requiredWithoutTag = "required_without"
requiredWithTag = "required_with"
requiredWithAllTag = "required_with_all"
skipValidationTag = "-" skipValidationTag = "-"
diveTag = "dive" diveTag = "dive"
keysTag = "keys" keysTag = "keys"
@ -55,6 +59,11 @@ type CustomTypeFunc func(field reflect.Value) interface{}
// TagNameFunc allows for adding of a custom tag name parser // TagNameFunc allows for adding of a custom tag name parser
type TagNameFunc func(field reflect.StructField) string type TagNameFunc func(field reflect.StructField) string
type internalValidationFuncWrapper struct {
fn FuncCtx
runValidatinOnNil bool
}
// Validate contains the validator settings and cache // Validate contains the validator settings and cache
type Validate struct { type Validate struct {
tagName string tagName string
@ -65,7 +74,7 @@ type Validate struct {
structLevelFuncs map[reflect.Type]StructLevelFuncCtx structLevelFuncs map[reflect.Type]StructLevelFuncCtx
customFuncs map[reflect.Type]CustomTypeFunc customFuncs map[reflect.Type]CustomTypeFunc
aliases map[string]string aliases map[string]string
validations map[string]FuncCtx validations map[string]internalValidationFuncWrapper
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
tagCache *tagCache tagCache *tagCache
structCache *structCache structCache *structCache
@ -83,7 +92,7 @@ func New() *Validate {
v := &Validate{ v := &Validate{
tagName: defaultTagName, tagName: defaultTagName,
aliases: make(map[string]string, len(bakedInAliases)), aliases: make(map[string]string, len(bakedInAliases)),
validations: make(map[string]FuncCtx, len(bakedInValidators)), validations: make(map[string]internalValidationFuncWrapper, len(bakedInValidators)),
tagCache: tc, tagCache: tc,
structCache: sc, structCache: sc,
} }
@ -96,8 +105,14 @@ func New() *Validate {
// must copy validators for separate validations to be used in each instance // must copy validators for separate validations to be used in each instance
for k, val := range bakedInValidators { for k, val := range bakedInValidators {
switch k {
// these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour
case requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag:
_ = v.registerValidation(k, wrapFunc(val), true, true)
default:
// no need to error check here, baked in will always be valid // no need to error check here, baked in will always be valid
_ = v.registerValidation(k, wrapFunc(val), true) _ = v.registerValidation(k, wrapFunc(val), true, false)
}
} }
v.pool = &sync.Pool{ v.pool = &sync.Pool{
@ -140,18 +155,21 @@ func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) {
// NOTES: // NOTES:
// - if the key already exists, the previous validation function will be replaced. // - if the key already exists, the previous validation function will be replaced.
// - this method is not thread-safe it is intended that these all be registered prior to any validation // - this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterValidation(tag string, fn Func) error { func (v *Validate) RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error {
return v.RegisterValidationCtx(tag, wrapFunc(fn)) return v.RegisterValidationCtx(tag, wrapFunc(fn), callValidationEvenIfNull...)
} }
// RegisterValidationCtx does the same as RegisterValidation on accepts a FuncCtx validation // RegisterValidationCtx does the same as RegisterValidation on accepts a FuncCtx validation
// allowing context.Context validation support. // allowing context.Context validation support.
func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx) error { func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx, callValidationEvenIfNull ...bool) error {
return v.registerValidation(tag, fn, false) var nilCheckable bool
if len(callValidationEvenIfNull) > 0 {
nilCheckable = callValidationEvenIfNull[0]
}
return v.registerValidation(tag, fn, false, nilCheckable)
} }
func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool) error { func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool, nilCheckable bool) error {
if len(tag) == 0 { if len(tag) == 0 {
return errors.New("Function Key cannot be empty") return errors.New("Function Key cannot be empty")
} }
@ -161,13 +179,10 @@ func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool) erro
} }
_, ok := restrictedTags[tag] _, ok := restrictedTags[tag]
if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) { if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) {
panic(fmt.Sprintf(restrictedTagErr, tag)) panic(fmt.Sprintf(restrictedTagErr, tag))
} }
v.validations[tag] = internalValidationFuncWrapper{fn: fn, runValidatinOnNil: nilCheckable}
v.validations[tag] = fn
return nil return nil
} }

@ -462,6 +462,8 @@ func TestAnonymous(t *testing.T) {
}, },
} }
Equal(t, tst.anonymousC.c, "")
err := validate.Struct(tst) err := validate.Struct(tst)
NotEqual(t, err, nil) NotEqual(t, err, nil)
@ -715,16 +717,15 @@ func TestNilValidator(t *testing.T) {
} }
PanicMatches(t, func() { val.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) }, "runtime error: invalid memory address or nil pointer dereference") PanicMatches(t, func() { val.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) }, "runtime error: invalid memory address or nil pointer dereference")
PanicMatches(t, func() { val.RegisterValidation("something", fn) }, "runtime error: invalid memory address or nil pointer dereference") PanicMatches(t, func() { _ = val.RegisterValidation("something", fn) }, "runtime error: invalid memory address or nil pointer dereference")
PanicMatches(t, func() { val.Var(ts.Test, "required") }, "runtime error: invalid memory address or nil pointer dereference") PanicMatches(t, func() { _ = val.Var(ts.Test, "required") }, "runtime error: invalid memory address or nil pointer dereference")
PanicMatches(t, func() { val.VarWithValue("test", ts.Test, "required") }, "runtime error: invalid memory address or nil pointer dereference") PanicMatches(t, func() { _ = val.VarWithValue("test", ts.Test, "required") }, "runtime error: invalid memory address or nil pointer dereference")
PanicMatches(t, func() { val.Struct(ts) }, "runtime error: invalid memory address or nil pointer dereference") PanicMatches(t, func() { _ = val.Struct(ts) }, "runtime error: invalid memory address or nil pointer dereference")
PanicMatches(t, func() { val.StructExcept(ts, "Test") }, "runtime error: invalid memory address or nil pointer dereference") PanicMatches(t, func() { _ = val.StructExcept(ts, "Test") }, "runtime error: invalid memory address or nil pointer dereference")
PanicMatches(t, func() { val.StructPartial(ts, "Test") }, "runtime error: invalid memory address or nil pointer dereference") PanicMatches(t, func() { _ = val.StructPartial(ts, "Test") }, "runtime error: invalid memory address or nil pointer dereference")
} }
func TestStructPartial(t *testing.T) { func TestStructPartial(t *testing.T) {
p1 := []string{ p1 := []string{
"NoTag", "NoTag",
"Required", "Required",
@ -1850,7 +1851,7 @@ func TestSQLValue2Validation(t *testing.T) {
val.Name = "errorme" val.Name = "errorme"
PanicMatches(t, func() { validate.Var(val, "required") }, "SQL Driver Valuer error: some kind of error") PanicMatches(t, func() { _ = validate.Var(val, "required") }, "SQL Driver Valuer error: some kind of error")
myVal := valuer{ myVal := valuer{
Name: "", Name: "",
@ -2675,7 +2676,7 @@ func TestBadKeyValidation(t *testing.T) {
validate := New() validate := New()
PanicMatches(t, func() { validate.Struct(tst) }, "Undefined validation function ' ' on field 'Name'") PanicMatches(t, func() { _ = validate.Struct(tst) }, "Undefined validation function ' ' on field 'Name'")
type Test2 struct { type Test2 struct {
Name string `validate:"required,,len=2"` Name string `validate:"required,,len=2"`
@ -2685,7 +2686,7 @@ func TestBadKeyValidation(t *testing.T) {
Name: "test", Name: "test",
} }
PanicMatches(t, func() { validate.Struct(tst2) }, "Invalid validation tag on field 'Name'") PanicMatches(t, func() { _ = validate.Struct(tst2) }, "Invalid validation tag on field 'Name'")
} }
func TestInterfaceErrValidation(t *testing.T) { func TestInterfaceErrValidation(t *testing.T) {
@ -3009,7 +3010,7 @@ func TestArrayDiveValidation(t *testing.T) {
Name: "TEST", Name: "TEST",
} }
PanicMatches(t, func() { validate.Struct(bd) }, "dive error! can't dive on a non slice or map") PanicMatches(t, func() { _ = validate.Struct(bd) }, "dive error! can't dive on a non slice or map")
type Test struct { type Test struct {
Errs []string `validate:"gt=0,dive,required"` Errs []string `validate:"gt=0,dive,required"`
@ -3372,7 +3373,7 @@ func TestLongitudeValidation(t *testing.T) {
} }
} }
PanicMatches(t, func() { validate.Var(true, "longitude") }, "Bad field type bool") PanicMatches(t, func() { _ = validate.Var(true, "longitude") }, "Bad field type bool")
} }
func TestLatitudeValidation(t *testing.T) { func TestLatitudeValidation(t *testing.T) {
@ -3414,7 +3415,7 @@ func TestLatitudeValidation(t *testing.T) {
} }
} }
PanicMatches(t, func() { validate.Var(true, "latitude") }, "Bad field type bool") PanicMatches(t, func() { _ = validate.Var(true, "latitude") }, "Bad field type bool")
} }
func TestDataURIValidation(t *testing.T) { func TestDataURIValidation(t *testing.T) {
@ -4317,7 +4318,7 @@ func TestIsNeValidation(t *testing.T) {
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "ne") AssertError(t, errs, "", "", "", "", "ne")
PanicMatches(t, func() { validate.Var(now, "ne=now") }, "Bad field type time.Time") PanicMatches(t, func() { _ = validate.Var(now, "ne=now") }, "Bad field type time.Time")
} }
func TestIsEqFieldValidation(t *testing.T) { func TestIsEqFieldValidation(t *testing.T) {
@ -4471,7 +4472,7 @@ func TestIsEqValidation(t *testing.T) {
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "eq") AssertError(t, errs, "", "", "", "", "eq")
PanicMatches(t, func() { validate.Var(now, "eq=now") }, "Bad field type time.Time") PanicMatches(t, func() { _ = validate.Var(now, "eq=now") }, "Bad field type time.Time")
} }
func TestOneOfValidation(t *testing.T) { func TestOneOfValidation(t *testing.T) {
@ -4530,7 +4531,7 @@ func TestOneOfValidation(t *testing.T) {
} }
PanicMatches(t, func() { PanicMatches(t, func() {
validate.Var(3.14, "oneof=red green") _ = validate.Var(3.14, "oneof=red green")
}, "Bad field type float64") }, "Bad field type float64")
} }
@ -4634,7 +4635,7 @@ func TestFileValidation(t *testing.T) {
} }
PanicMatches(t, func() { PanicMatches(t, func() {
validate.Var(6, "file") _ = validate.Var(6, "file")
}, "Bad field type int") }, "Bad field type int")
} }
@ -5724,7 +5725,6 @@ func TestGteField(t *testing.T) {
} }
func TestValidateByTagAndValue(t *testing.T) { func TestValidateByTagAndValue(t *testing.T) {
validate := New() validate := New()
val := "test" val := "test"
@ -5737,7 +5737,8 @@ func TestValidateByTagAndValue(t *testing.T) {
return fl.Parent().String() == fl.Field().String() return fl.Parent().String() == fl.Field().String()
} }
validate.RegisterValidation("isequaltestfunc", fn) errs = validate.RegisterValidation("isequaltestfunc", fn)
Equal(t, errs, nil)
errs = validate.VarWithValue(val, field, "isequaltestfunc") errs = validate.VarWithValue(val, field, "isequaltestfunc")
Equal(t, errs, nil) Equal(t, errs, nil)
@ -5768,7 +5769,7 @@ func TestAddFunctions(t *testing.T) {
errs = validate.RegisterValidation("", fn) errs = validate.RegisterValidation("", fn)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
validate.RegisterValidation("new", nil) errs = validate.RegisterValidation("new", nil)
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
errs = validate.RegisterValidation("new", fn) errs = validate.RegisterValidation("new", fn)
@ -5777,7 +5778,7 @@ func TestAddFunctions(t *testing.T) {
errs = validate.RegisterValidationCtx("new", fnCtx) errs = validate.RegisterValidationCtx("new", fnCtx)
Equal(t, errs, nil) Equal(t, errs, nil)
PanicMatches(t, func() { validate.RegisterValidation("dive", fn) }, "Tag 'dive' either contains restricted characters or is the same as a restricted tag needed for normal operation") PanicMatches(t, func() { _ = validate.RegisterValidation("dive", fn) }, "Tag 'dive' either contains restricted characters or is the same as a restricted tag needed for normal operation")
} }
func TestChangeTag(t *testing.T) { func TestChangeTag(t *testing.T) {
@ -5803,7 +5804,6 @@ func TestChangeTag(t *testing.T) {
} }
func TestUnexposedStruct(t *testing.T) { func TestUnexposedStruct(t *testing.T) {
validate := New() validate := New()
type Test struct { type Test struct {
@ -5816,41 +5816,36 @@ func TestUnexposedStruct(t *testing.T) {
s := &Test{ s := &Test{
Name: "TEST", Name: "TEST",
} }
Equal(t, s.unexposed.A, "")
errs := validate.Struct(s) errs := validate.Struct(s)
Equal(t, errs, nil) Equal(t, errs, nil)
} }
func TestBadParams(t *testing.T) { func TestBadParams(t *testing.T) {
validate := New() validate := New()
i := 1 i := 1
errs := validate.Var(i, "-") errs := validate.Var(i, "-")
Equal(t, errs, nil) Equal(t, errs, nil)
PanicMatches(t, func() { validate.Var(i, "len=a") }, "strconv.ParseInt: parsing \"a\": invalid syntax") PanicMatches(t, func() { _ = validate.Var(i, "len=a") }, "strconv.ParseInt: parsing \"a\": invalid syntax")
PanicMatches(t, func() { validate.Var(i, "len=a") }, "strconv.ParseInt: parsing \"a\": invalid syntax") PanicMatches(t, func() { _ = validate.Var(i, "len=a") }, "strconv.ParseInt: parsing \"a\": invalid syntax")
var ui uint = 1 var ui uint = 1
PanicMatches(t, func() { validate.Var(ui, "len=a") }, "strconv.ParseUint: parsing \"a\": invalid syntax") PanicMatches(t, func() { _ = validate.Var(ui, "len=a") }, "strconv.ParseUint: parsing \"a\": invalid syntax")
f := 1.23 f := 1.23
PanicMatches(t, func() { validate.Var(f, "len=a") }, "strconv.ParseFloat: parsing \"a\": invalid syntax") PanicMatches(t, func() { _ = validate.Var(f, "len=a") }, "strconv.ParseFloat: parsing \"a\": invalid syntax")
} }
func TestLength(t *testing.T) { func TestLength(t *testing.T) {
validate := New() validate := New()
i := true i := true
PanicMatches(t, func() { validate.Var(i, "len") }, "Bad field type bool") PanicMatches(t, func() { _ = validate.Var(i, "len") }, "Bad field type bool")
} }
func TestIsGt(t *testing.T) { func TestIsGt(t *testing.T) {
validate := New() validate := New()
myMap := map[string]string{} myMap := map[string]string{}
errs := validate.Var(myMap, "gt=0") errs := validate.Var(myMap, "gt=0")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
@ -5866,7 +5861,7 @@ func TestIsGt(t *testing.T) {
AssertError(t, errs, "", "", "", "", "gt") AssertError(t, errs, "", "", "", "", "gt")
i := true i := true
PanicMatches(t, func() { validate.Var(i, "gt") }, "Bad field type bool") PanicMatches(t, func() { _ = validate.Var(i, "gt") }, "Bad field type bool")
tm := time.Now().UTC() tm := time.Now().UTC()
tm = tm.Add(time.Hour * 24) tm = tm.Add(time.Hour * 24)
@ -5900,11 +5895,9 @@ func TestIsGt(t *testing.T) {
} }
func TestIsGte(t *testing.T) { func TestIsGte(t *testing.T) {
validate := New() validate := New()
i := true i := true
PanicMatches(t, func() { validate.Var(i, "gte") }, "Bad field type bool") PanicMatches(t, func() { _ = validate.Var(i, "gte") }, "Bad field type bool")
t1 := time.Now().UTC() t1 := time.Now().UTC()
t1 = t1.Add(time.Hour * 24) t1 = t1.Add(time.Hour * 24)
@ -5938,9 +5931,7 @@ func TestIsGte(t *testing.T) {
} }
func TestIsLt(t *testing.T) { func TestIsLt(t *testing.T) {
validate := New() validate := New()
myMap := map[string]string{} myMap := map[string]string{}
errs := validate.Var(myMap, "lt=0") errs := validate.Var(myMap, "lt=0")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
@ -5957,7 +5948,7 @@ func TestIsLt(t *testing.T) {
AssertError(t, errs, "", "", "", "", "lt") AssertError(t, errs, "", "", "", "", "lt")
i := true i := true
PanicMatches(t, func() { validate.Var(i, "lt") }, "Bad field type bool") PanicMatches(t, func() { _ = validate.Var(i, "lt") }, "Bad field type bool")
t1 := time.Now().UTC().Add(-time.Hour) t1 := time.Now().UTC().Add(-time.Hour)
@ -5996,7 +5987,7 @@ func TestIsLte(t *testing.T) {
validate := New() validate := New()
i := true i := true
PanicMatches(t, func() { validate.Var(i, "lte") }, "Bad field type bool") PanicMatches(t, func() { _ = validate.Var(i, "lte") }, "Bad field type bool")
t1 := time.Now().UTC().Add(-time.Hour) t1 := time.Now().UTC().Add(-time.Hour)
@ -6103,7 +6094,7 @@ func TestUrnRFC2141(t *testing.T) {
} }
i := 1 i := 1
PanicMatches(t, func() { validate.Var(i, tag) }, "Bad field type int") PanicMatches(t, func() { _ = validate.Var(i, tag) }, "Bad field type int")
} }
func TestUrl(t *testing.T) { func TestUrl(t *testing.T) {
@ -6171,7 +6162,7 @@ func TestUrl(t *testing.T) {
} }
i := 1 i := 1
PanicMatches(t, func() { validate.Var(i, "url") }, "Bad field type int") PanicMatches(t, func() { _ = validate.Var(i, "url") }, "Bad field type int")
} }
func TestUri(t *testing.T) { func TestUri(t *testing.T) {
@ -6238,7 +6229,7 @@ func TestUri(t *testing.T) {
} }
i := 1 i := 1
PanicMatches(t, func() { validate.Var(i, "uri") }, "Bad field type int") PanicMatches(t, func() { _ = validate.Var(i, "uri") }, "Bad field type int")
} }
func TestOrTag(t *testing.T) { func TestOrTag(t *testing.T) {
@ -6280,8 +6271,8 @@ func TestOrTag(t *testing.T) {
s = "this is right, but a blank or isn't" s = "this is right, but a blank or isn't"
PanicMatches(t, func() { validate.Var(s, "rgb||len=13") }, "Invalid validation tag on field ''") PanicMatches(t, func() { _ = validate.Var(s, "rgb||len=13") }, "Invalid validation tag on field ''")
PanicMatches(t, func() { validate.Var(s, "rgb|rgbaa|len=13") }, "Undefined validation function 'rgbaa' on field ''") PanicMatches(t, func() { _ = validate.Var(s, "rgb|rgbaa|len=13") }, "Undefined validation function 'rgbaa' on field ''")
v2 := New() v2 := New()
v2.RegisterTagNameFunc(func(fld reflect.StructField) string { v2.RegisterTagNameFunc(func(fld reflect.StructField) string {
@ -6351,7 +6342,7 @@ func TestHsla(t *testing.T) {
AssertError(t, errs, "", "", "", "", "hsla") AssertError(t, errs, "", "", "", "", "hsla")
i := 1 i := 1
validate.Var(i, "hsla") errs = validate.Var(i, "hsla")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "hsla") AssertError(t, errs, "", "", "", "", "hsla")
} }
@ -7053,7 +7044,7 @@ func TestInvalidValidatorFunction(t *testing.T) {
Test: "1", Test: "1",
} }
PanicMatches(t, func() { validate.Var(s.Test, "zzxxBadFunction") }, "Undefined validation function 'zzxxBadFunction' on field ''") PanicMatches(t, func() { _ = validate.Var(s.Test, "zzxxBadFunction") }, "Undefined validation function 'zzxxBadFunction' on field ''")
} }
func TestCustomFieldName(t *testing.T) { func TestCustomFieldName(t *testing.T) {
@ -7309,15 +7300,15 @@ func TestTranslations(t *testing.T) {
} }
func TestTranslationErrors(t *testing.T) { func TestTranslationErrors(t *testing.T) {
en := en.New() en := en.New()
uni := ut.New(en, en, fr.New()) uni := ut.New(en, en, fr.New())
trans, _ := uni.GetTranslator("en") trans, _ := uni.GetTranslator("en")
trans.Add("required", "{0} is a required field", false) // using translator outside of validator also err := trans.Add("required", "{0} is a required field", false) // using translator outside of validator also
Equal(t, err, nil)
validate := New() validate := New()
err := validate.RegisterTranslation("required", trans, err = validate.RegisterTranslation("required", trans,
func(ut ut.Translator) (err error) { func(ut ut.Translator) (err error) {
// using this stype because multiple translation may have to be added for the full translation // using this stype because multiple translation may have to be added for the full translation
@ -7766,31 +7757,40 @@ func TestFieldLevelName(t *testing.T) {
return name return name
}) })
validate.RegisterValidation("custom1", func(fl FieldLevel) bool { err := validate.RegisterValidation("custom1", func(fl FieldLevel) bool {
res1 = fl.FieldName() res1 = fl.FieldName()
alt1 = fl.StructFieldName() alt1 = fl.StructFieldName()
return true return true
}) })
validate.RegisterValidation("custom2", func(fl FieldLevel) bool { Equal(t, err, nil)
err = validate.RegisterValidation("custom2", func(fl FieldLevel) bool {
res2 = fl.FieldName() res2 = fl.FieldName()
alt2 = fl.StructFieldName() alt2 = fl.StructFieldName()
return true return true
}) })
validate.RegisterValidation("custom3", func(fl FieldLevel) bool { Equal(t, err, nil)
err = validate.RegisterValidation("custom3", func(fl FieldLevel) bool {
res3 = fl.FieldName() res3 = fl.FieldName()
alt3 = fl.StructFieldName() alt3 = fl.StructFieldName()
return true return true
}) })
validate.RegisterValidation("custom4", func(fl FieldLevel) bool { Equal(t, err, nil)
err = validate.RegisterValidation("custom4", func(fl FieldLevel) bool {
res4 = fl.FieldName() res4 = fl.FieldName()
alt4 = fl.StructFieldName() alt4 = fl.StructFieldName()
return true return true
}) })
validate.RegisterValidation("custom5", func(fl FieldLevel) bool { Equal(t, err, nil)
err = validate.RegisterValidation("custom5", func(fl FieldLevel) bool {
res5 = fl.FieldName() res5 = fl.FieldName()
alt5 = fl.StructFieldName() alt5 = fl.StructFieldName()
return true return true
}) })
Equal(t, err, nil)
test := Test{ test := Test{
String: "test", String: "test",
@ -7813,7 +7813,6 @@ func TestFieldLevelName(t *testing.T) {
} }
func TestValidateStructRegisterCtx(t *testing.T) { func TestValidateStructRegisterCtx(t *testing.T) {
var ctxVal string var ctxVal string
fnCtx := func(ctx context.Context, fl FieldLevel) bool { fnCtx := func(ctx context.Context, fl FieldLevel) bool {
@ -7833,7 +7832,9 @@ func TestValidateStructRegisterCtx(t *testing.T) {
var tst Test var tst Test
validate := New() validate := New()
validate.RegisterValidationCtx("val", fnCtx) err := validate.RegisterValidationCtx("val", fnCtx)
Equal(t, err, nil)
validate.RegisterStructValidationCtx(slFn, Test{}) validate.RegisterStructValidationCtx(slFn, Test{})
ctx := context.WithValue(context.Background(), &ctxVal, "testval") ctx := context.WithValue(context.Background(), &ctxVal, "testval")
@ -8039,7 +8040,6 @@ func TestFQDNValidation(t *testing.T) {
} }
func TestIsDefault(t *testing.T) { func TestIsDefault(t *testing.T) {
validate := New() validate := New()
type Inner struct { type Inner struct {
@ -8066,11 +8066,9 @@ func TestIsDefault(t *testing.T) {
validate.RegisterTagNameFunc(func(fld reflect.StructField) string { validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" { if name == "-" {
return "" return ""
} }
return name return name
}) })
@ -8157,7 +8155,7 @@ func TestUniqueValidation(t *testing.T) {
} }
} }
} }
PanicMatches(t, func() { validate.Var(1.0, "unique") }, "Bad field type float64") PanicMatches(t, func() { _ = validate.Var(1.0, "unique") }, "Bad field type float64")
} }
func TestHTMLValidation(t *testing.T) { func TestHTMLValidation(t *testing.T) {
@ -8362,8 +8360,8 @@ func TestKeys(t *testing.T) {
// test bad tag definitions // test bad tag definitions
PanicMatches(t, func() { validate.Var(map[string]string{"key": "val"}, "endkeys,dive,eq=val") }, "'endkeys' tag encountered without a corresponding 'keys' tag") PanicMatches(t, func() { _ = validate.Var(map[string]string{"key": "val"}, "endkeys,dive,eq=val") }, "'endkeys' tag encountered without a corresponding 'keys' tag")
PanicMatches(t, func() { validate.Var(1, "keys,eq=1,endkeys") }, "'keys' tag must be immediately preceded by the 'dive' tag") PanicMatches(t, func() { _ = validate.Var(1, "keys,eq=1,endkeys") }, "'keys' tag must be immediately preceded by the 'dive' tag")
// test custom tag name // test custom tag name
validate = New() validate = New()
@ -8391,7 +8389,6 @@ func TestKeys(t *testing.T) {
// Thanks @adrian-sgn specific test for your specific scenario // Thanks @adrian-sgn specific test for your specific scenario
func TestKeysCustomValidation(t *testing.T) { func TestKeysCustomValidation(t *testing.T) {
type LangCode string type LangCode string
type Label map[LangCode]string type Label map[LangCode]string
@ -8400,7 +8397,7 @@ func TestKeysCustomValidation(t *testing.T) {
} }
validate := New() validate := New()
validate.RegisterValidation("lang_code", func(fl FieldLevel) bool { err := validate.RegisterValidation("lang_code", func(fl FieldLevel) bool {
validLangCodes := map[LangCode]struct{}{ validLangCodes := map[LangCode]struct{}{
"en": {}, "en": {},
"es": {}, "es": {},
@ -8410,6 +8407,7 @@ func TestKeysCustomValidation(t *testing.T) {
_, ok := validLangCodes[fl.Field().Interface().(LangCode)] _, ok := validLangCodes[fl.Field().Interface().(LangCode)]
return ok return ok
}) })
Equal(t, err, nil)
label := Label{ label := Label{
"en": "Good morning!", "en": "Good morning!",
@ -8419,7 +8417,7 @@ func TestKeysCustomValidation(t *testing.T) {
"xxx": "", "xxx": "",
} }
err := validate.Struct(TestMapStructPtr{label}) err = validate.Struct(TestMapStructPtr{label})
NotEqual(t, err, nil) NotEqual(t, err, nil)
errs := err.(ValidationErrors) errs := err.(ValidationErrors)
@ -8562,7 +8560,7 @@ func TestDirValidation(t *testing.T) {
} }
PanicMatches(t, func() { PanicMatches(t, func() {
validate.Var(2, "dir") _ = validate.Var(2, "dir")
}, "Bad field type int") }, "Bad field type int")
} }
@ -8621,17 +8619,22 @@ func TestEndsWithValidation(t *testing.T) {
} }
func TestRequiredWith(t *testing.T) { func TestRequiredWith(t *testing.T) {
type Inner struct {
Field *string
}
fieldVal := "test" fieldVal := "test"
test := struct { test := struct {
Inner *Inner
FieldE string `validate:"omitempty" json:"field_e"` FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"required_with=FieldE" json:"field_er"` FieldER string `validate:"required_with=FieldE" json:"field_er"`
Field1 string `validate:"omitempty" json:"field_1"` Field1 string `validate:"omitempty" json:"field_1"`
Field2 *string `validate:"required_with=Field1" json:"field_2"` Field2 *string `validate:"required_with=Field1" json:"field_2"`
Field3 map[string]string `validate:"required_with=Field2" json:"field_3"` Field3 map[string]string `validate:"required_with=Field2" json:"field_3"`
Field4 interface{} `validate:"required_with=Field3" json:"field_4"` Field4 interface{} `validate:"required_with=Field3" json:"field_4"`
Field5 string `validate:"required_with=Field3" json:"field_5"` Field5 string `validate:"required_with=Inner.Field" json:"field_5"`
}{ }{
Field1: "test_field1", Inner: &Inner{Field: &fieldVal},
Field2: &fieldVal, Field2: &fieldVal,
Field3: map[string]string{"key": "val"}, Field3: map[string]string{"key": "val"},
Field4: "test", Field4: "test",
@ -8641,24 +8644,52 @@ func TestRequiredWith(t *testing.T) {
validate := New() validate := New()
errs := validate.Struct(test) errs := validate.Struct(test)
Equal(t, errs, nil)
if errs != nil { test2 := struct {
t.Fatalf("failed Error: %s", errs) Inner *Inner
Inner2 *Inner
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"required_with=FieldE" json:"field_er"`
Field1 string `validate:"omitempty" json:"field_1"`
Field2 *string `validate:"required_with=Field1" json:"field_2"`
Field3 map[string]string `validate:"required_with=Field2" json:"field_3"`
Field4 interface{} `validate:"required_with=Field2" json:"field_4"`
Field5 string `validate:"required_with=Field3" json:"field_5"`
Field6 string `validate:"required_with=Inner.Field" json:"field_6"`
Field7 string `validate:"required_with=Inner2.Field" json:"field_7"`
}{
Inner: &Inner{Field: &fieldVal},
Field2: &fieldVal,
} }
errs = validate.Struct(test2)
NotEqual(t, errs, nil)
ve := errs.(ValidationErrors)
Equal(t, len(ve), 3)
AssertError(t, errs, "Field3", "Field3", "Field3", "Field3", "required_with")
AssertError(t, errs, "Field4", "Field4", "Field4", "Field4", "required_with")
AssertError(t, errs, "Field6", "Field6", "Field6", "Field6", "required_with")
} }
func TestRequiredWithAll(t *testing.T) { func TestRequiredWithAll(t *testing.T) {
type Inner struct {
Field *string
}
fieldVal := "test" fieldVal := "test"
test := struct { test := struct {
Inner *Inner
FieldE string `validate:"omitempty" json:"field_e"` FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"required_with_all=FieldE" json:"field_er"` FieldER string `validate:"required_with_all=FieldE" json:"field_er"`
Field1 string `validate:"omitempty" json:"field_1"` Field1 string `validate:"omitempty" json:"field_1"`
Field2 *string `validate:"required_with_all=Field1" json:"field_2"` Field2 *string `validate:"required_with_all=Field1" json:"field_2"`
Field3 map[string]string `validate:"required_with_all=Field2" json:"field_3"` Field3 map[string]string `validate:"required_with_all=Field2" json:"field_3"`
Field4 interface{} `validate:"required_with_all=Field3" json:"field_4"` Field4 interface{} `validate:"required_with_all=Field3" json:"field_4"`
Field5 string `validate:"required_with_all=Field3" json:"field_5"` Field5 string `validate:"required_with_all=Inner.Field" json:"field_5"`
}{ }{
Inner: &Inner{Field: &fieldVal},
Field1: "test_field1", Field1: "test_field1",
Field2: &fieldVal, Field2: &fieldVal,
Field3: map[string]string{"key": "val"}, Field3: map[string]string{"key": "val"},
@ -8669,22 +8700,49 @@ func TestRequiredWithAll(t *testing.T) {
validate := New() validate := New()
errs := validate.Struct(test) errs := validate.Struct(test)
Equal(t, errs, nil)
if errs != nil { test2 := struct {
t.Fatalf("failed Error: %s", errs) Inner *Inner
Inner2 *Inner
FieldE string `validate:"omitempty" json:"field_e"`
FieldER string `validate:"required_with_all=FieldE" json:"field_er"`
Field1 string `validate:"omitempty" json:"field_1"`
Field2 *string `validate:"required_with_all=Field1" json:"field_2"`
Field3 map[string]string `validate:"required_with_all=Field2" json:"field_3"`
Field4 interface{} `validate:"required_with_all=Field1 FieldE" json:"field_4"`
Field5 string `validate:"required_with_all=Inner.Field Field2" json:"field_5"`
Field6 string `validate:"required_with_all=Inner2.Field Field2" json:"field_6"`
}{
Inner: &Inner{Field: &fieldVal},
Field2: &fieldVal,
} }
errs = validate.Struct(test2)
NotEqual(t, errs, nil)
ve := errs.(ValidationErrors)
Equal(t, len(ve), 2)
AssertError(t, errs, "Field3", "Field3", "Field3", "Field3", "required_with_all")
AssertError(t, errs, "Field5", "Field5", "Field5", "Field5", "required_with_all")
} }
func TestRequiredWithout(t *testing.T) { func TestRequiredWithout(t *testing.T) {
type Inner struct {
Field *string
}
fieldVal := "test" fieldVal := "test"
test := struct { test := struct {
Inner *Inner
Field1 string `validate:"omitempty" json:"field_1"` Field1 string `validate:"omitempty" json:"field_1"`
Field2 *string `validate:"required_without=Field1" json:"field_2"` Field2 *string `validate:"required_without=Field1" json:"field_2"`
Field3 map[string]string `validate:"required_without=Field2" json:"field_3"` Field3 map[string]string `validate:"required_without=Field2" json:"field_3"`
Field4 interface{} `validate:"required_without=Field3" json:"field_4"` Field4 interface{} `validate:"required_without=Field3" json:"field_4"`
Field5 string `validate:"required_without=Field3" json:"field_5"` Field5 string `validate:"required_without=Field3" json:"field_5"`
}{ }{
Inner: &Inner{Field: &fieldVal},
Field2: &fieldVal, Field2: &fieldVal,
Field3: map[string]string{"key": "val"}, Field3: map[string]string{"key": "val"},
Field4: "test", Field4: "test",
@ -8694,29 +8752,35 @@ func TestRequiredWithout(t *testing.T) {
validate := New() validate := New()
errs := validate.Struct(test) errs := validate.Struct(test)
Equal(t, errs, nil)
if errs != nil {
t.Fatalf("failed Error: %s", errs)
}
test2 := struct { test2 := struct {
Field1 string `validate:"omitempty" json:"field_1"` Inner *Inner
Inner2 *Inner
Field1 string `json:"field_1"`
Field2 *string `validate:"required_without=Field1" json:"field_2"` Field2 *string `validate:"required_without=Field1" json:"field_2"`
Field3 map[string]string `validate:"required_without=Field2" json:"field_3"` Field3 map[string]string `validate:"required_without=Field2" json:"field_3"`
Field4 interface{} `validate:"required_without=Field3" json:"field_4"` Field4 interface{} `validate:"required_without=Field3" json:"field_4"`
Field5 string `validate:"required_without=Field3" json:"field_5"` Field5 string `validate:"required_without=Field3" json:"field_5"`
Field6 string `validate:"required_without=Field1" json:"field_6"` Field6 string `validate:"required_without=Field1" json:"field_6"`
Field7 string `validate:"required_without=Inner.Field" json:"field_7"`
Field8 string `validate:"required_without=Inner.Field" json:"field_8"`
}{ }{
Inner: &Inner{},
Field3: map[string]string{"key": "val"}, Field3: map[string]string{"key": "val"},
Field4: "test", Field4: "test",
Field5: "test", Field5: "test",
} }
errs = validate.Struct(&test2) errs = validate.Struct(&test2)
NotEqual(t, errs, nil)
if errs == nil { ve := errs.(ValidationErrors)
t.Fatalf("failed Error: %s", errs) Equal(t, len(ve), 4)
} AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without")
AssertError(t, errs, "Field6", "Field6", "Field6", "Field6", "required_without")
AssertError(t, errs, "Field7", "Field7", "Field7", "Field7", "required_without")
AssertError(t, errs, "Field8", "Field8", "Field8", "Field8", "required_without")
} }
func TestRequiredWithoutAll(t *testing.T) { func TestRequiredWithoutAll(t *testing.T) {
@ -8739,10 +8803,7 @@ func TestRequiredWithoutAll(t *testing.T) {
validate := New() validate := New()
errs := validate.Struct(test) errs := validate.Struct(test)
Equal(t, errs, nil)
if errs != nil {
t.Fatalf("failed Error: %s", errs)
}
test2 := struct { test2 := struct {
Field1 string `validate:"omitempty" json:"field_1"` Field1 string `validate:"omitempty" json:"field_1"`
@ -8750,7 +8811,7 @@ func TestRequiredWithoutAll(t *testing.T) {
Field3 map[string]string `validate:"required_without_all=Field2" json:"field_3"` Field3 map[string]string `validate:"required_without_all=Field2" json:"field_3"`
Field4 interface{} `validate:"required_without_all=Field3" json:"field_4"` Field4 interface{} `validate:"required_without_all=Field3" json:"field_4"`
Field5 string `validate:"required_without_all=Field3" json:"field_5"` Field5 string `validate:"required_without_all=Field3" json:"field_5"`
Field6 string `validate:"required_without_all=Field1" json:"field_6"` Field6 string `validate:"required_without_all=Field1 Field3" json:"field_6"`
}{ }{
Field3: map[string]string{"key": "val"}, Field3: map[string]string{"key": "val"},
Field4: "test", Field4: "test",
@ -8758,8 +8819,48 @@ func TestRequiredWithoutAll(t *testing.T) {
} }
errs = validate.Struct(test2) errs = validate.Struct(test2)
NotEqual(t, errs, nil)
if errs == nil { ve := errs.(ValidationErrors)
t.Fatalf("failed Error: %s", errs) Equal(t, len(ve), 1)
AssertError(t, errs, "Field2", "Field2", "Field2", "Field2", "required_without_all")
}
func TestLookup(t *testing.T) {
type Lookup struct {
FieldA *string `json:"fieldA,omitempty" validate:"required_without=FieldB"`
FieldB *string `json:"fieldB,omitempty" validate:"required_without=FieldA"`
}
fieldAValue := "1232"
lookup := Lookup{
FieldA: &fieldAValue,
FieldB: nil,
}
Equal(t, New().Struct(lookup), nil)
}
func TestAbilityToValidateNils(t *testing.T) {
type TestStruct struct {
Test *string `validate:"nil"`
}
ts := TestStruct{}
val := New()
fn := func(fl FieldLevel) bool {
return fl.Field().Kind() == reflect.Ptr && fl.Field().IsNil()
} }
err := val.RegisterValidation("nil", fn, true)
Equal(t, err, nil)
errs := val.Struct(ts)
Equal(t, errs, nil)
str := "string"
ts.Test = &str
errs = val.Struct(ts)
NotEqual(t, errs, nil)
} }

Loading…
Cancel
Save